第3章 教学设备管理系统

东方学院教学设备管理系统是为教学器材设备组开发的电子化管理系统,它实现了教学设备的采购入库、外借、归还入库、查询统计等一体化的管理。实际使用表明,该系统实用、简便、有效。

本章的学习重点:

◆ ADO操作数据库。

◆ CADORecordBinding绑定ADO数据。

◆ SQL数据查询和数据操作语句。

3.1 开发背景

东方学院教学器材设备组主要负责整个学院的所有教学设备的统一采购、存储、管理工作。设备组所辖库房含有数百种教学设备,管理员每天要进行至少几十项设备的外借和归还入库工作。另外,还有针对用户的需求、仓库的库存情况实时进行必要的采购,以免影响教学工作,因此工作繁琐,工作量巨大。

以前采用的人工管理方式不能及时准确反映各类设备的库存状况,容易造成设备的积压和供应断档,也不能及时反映各部门的设备需求状况,进行必要的采购、清理和补充。为了解决这些问题,降低管理人员的劳动强度,这里开发了教学设备管理软件,初步实现了教学设备管理的信息化,达到了设备统计数据的实时、准确,提高了设备管理工作的效率。

3.2 系统分析

3.2.1 需求分析

根据教学器材设备组的运作模式,结合学院教学管理的其他要求,库房管理部门对设备管理软件提出了一系列要求,主要有:

● 用户界面简洁、友好。

● 采购入库、外借出库操作简便,且便于查询和修改。

● 设备的出、入库和库存数据动态化。即库存数据、入库数据、出库数据随出、入库操作的进行实时改变。

● 库存数据查询方便,修改方便,且能够实现必要的库存报警功能。

● 用户可以提前预约,提供设备需求信息。

● 管理员可以根据库存及用户需求信息,生成采购报表。

● 为确保安全,防止恶意操作,对于系统的每次操作,都写入日志中,便于查询与恢复。

3.2.2 功能分析

通过对用户需求的分析,该教学设备管理系统软件主要实现的功能如下。

● 设备采购:填写设备采购清单,采购设备入库。

● 设备外借:填写设备外借清单,设备出库。

● 设备归还:填写设备归还清单,设备入库。

● 设备预约:填写设备预约清单。

● 采购报表:填写设备采购报表,呈领导审批。

● 库存管理:查看库存信息,可以进行库存盘点。

● 查询功能:可以实现对采购、外借、归还、预约、报表操作记录进行查询、编辑修改操作。

● 操作日志:记录系统操作员的所有操作信息。

3.3 系统设计

3.3.1 绘制用例图设计系统功能

根据前面的系统功能分析,设备管理员的用例图可表示为如图3-1所示。

图3-1 设备管理员系统用例图

3.3.2 绘制系统流程图

通过对用户需求分析和系统的功能分析,教学设备管理系统软件的系统流程图可表示为,如图3-2所示。

图3-2 系统软件的构成及流程

该系统分设备采购入库、借还设备管理、设备需求统计、系统管理四个主功能模块,十六个子功能模块。

3.3.3 开发工具和开发技术的选择

本系统在Windows XP中文版操作系统环境下,使用Visual C++ 6.0中文版开发成功的,后台的数据库系统设计采用的是Micorsoft的Access数据库系统。

对数据库的开发技术,通过ODBC设置数据源,使用ADO技术操作数据库,并使用CADORecordBinding类绑定指定的记录集。

3.3.4 系统的运行环境

系统可以直接在Win98、Win2000、WinXP环境下运行。因为数据库的操作是通过ADO连接、操作ODBC数据源数据,因此需要在程序运行前设置ODBC数据源。

这里需要将Access数据库文件“Instrument.mdb”设置为数据源,名称为“InstrumentManage”。具体可通过ODBC数据源管理器来实现,详细设置过程可参考本书第1章1.3.4节的介绍。

3.3.5 系统演示

管理员登录教学设备管理系统后,会出现如图3-3所示的对话框。在该对话框中,可以通过提供的菜单命令项或是命令按钮执行相关的操作。下面以设备采购入库操作流程为例,简单做一下系统演示。

图3-3 主界面对话框

进行设备采购,首先要确认是否已为设备编码,若没有,单击“设备编码”按钮,系统会弹出如图3-4所示的设备代码管理对话框,在其中可以添加、修改和删除设备编码信息。

图3-4 “设备代码管理”对话框

如设备编码后,单击“采购设备”按钮,系统会弹出如图3-5所示的“设备购买登记”对话框。填写相关的信息,单击“确定”按钮后,即实现了设备的采购入库。

图3-5 “设备购买登记”对话框

管理员也可以查询、修改设备的采购入库信息。单击“采购查询”按钮,系统会弹出如图3-6所示的“查看入库信息”对话框,在其中可以查看、修改、删除设备的采购入库信息。

图3-6 “查看入库信息”对话框

其他操作与此类似,在下面具体开发时会有相关介绍。

3.3.6 系统类库设计

教学设备管理系统主框架的设计是通过MFC创建向导创建的基于对话框的窗口程序,工程名为“InstrumentManage”,系统的类库主要设计如下。

● ADO记录绑定类:为了便于对数据库表的操作,从CADORecordBinding类派生了一系列的类,将其成员变量绑定到一个指定的记录集,以方便访问记录集的字段值,如表3-1所示。

表3-1 ADO记录绑定类及功能

● 用户操作对话框类:教学设备管理系统所有的与用户的交互操作都是通过对话框窗口来实现的,这些对话框对应的类及说明如表3-2所示。

表3-2 用户操作对话框类及说明

● 主界面窗口、控件类:主界面类包含系统本身提供的框架类,另外还包含自定义按钮控件类,如表3-3所示。

表3-3 主界面类与自定义按钮控件类

3.4 数据库分析与设计

3.4.1 数据库分析

教学设备管理系统主要针对学院内部的教学设备进行管理,其系统的数据量较少,且为单机使用,日常访问操作的业务量也有限,因此为了便于项目开发和维护,这里后台的数据库系统设计采用的是Micorsoft的Access数据库系统。

3.4.2 数据库概念设计

通过前面对教学设备管理系统的分析,对设备的每种业务操作都应用一个数据实体,而这些实体都与设备编码实体相关,设备编码实体的E-R图如图3-7所示。

图3-7 设备编码实体的E-R图

与设备采购入库操作相关的实体为入库设备实体,其实体的E-R图如图3-8所示。

图3-8 入库设备实体的E-R图

与设备外借操作相关的实体为出库设备实体,其实体的E-R图如图3-9所示。

图3-9 出库设备实体的E-R图

与设备库存查询相关的实体为设备库存实体,其实体的E-R图如图3-10所示。

图3-10 设备库存实体的E-R图

与归还设备操作相关的实体为归还设备实体,其实体的E-R图如图3-11所示。

图3-11 归还设备实体的E-R图

与制定采购报表操作相关的实体为采购报表实体,其实体的E-R图如图3-12所示。

图3-12 采购报表实体的E-R图

3.4.3 数据库逻辑结构设计

根据前面设计好的各实体E-R图,就可以创建相关的数据库的逻辑结构,本系统数据库共创建了8个表,下面分别介绍。

device_code表用于记录设备的编码信息,该表的逻辑结构如表3-4所示。

表3-4 device_code表字段描述

device_in表用于记录设备采购入库登记信息,该表的逻辑结构如表3-5所示。

表3-5 device_in表字段描述

device_out表用于记录设备外借登记信息,该表的逻辑结构如表3-6所示。

表3-6 device_out表字段描述

device_info表用于记录库存设备信息,该表的逻辑结构如表3-7所示。

表3-7 device_info表字段描述

device_return表用于记录归还设备信息,该表的逻辑结构如表3-8所示。

表3-8 device_return表字段描述

device_need表用于记录用户预约设备信息,该表的逻辑结构如表3-9所示。

表3-9 device_need表字段描述

device_wantbuy表用于记录采购设备报表信息,该表的逻辑结构如表3-10所示。

表3-10 device_wantbuy表字段描述

user_operation记录用户的操作日志,该表的逻辑结构如表3-11所示。

表3-11 user_operation表字段描述

3.4.4 数据库的创建

各表设计完毕后就可以创建数据库。在Access 2000中,首先创建数据库Instrument,然后就可以在该数据库中创建各表,最终创建结果如图3-13所示。

图3-13 Access创建数据库和表

创建完毕后,即得到数据库文件“Instrument.mdb”。另外,还需要在Access中设置各表之间的关系,如图3-14所示。

图3-14 各表之间的关系

3.5 公共类设计

在本系统中,对数据库的操作是通过采用ADO组件来实现的。为了便于操作,系统设置了系列ADO绑定类和日志管理类。

3.5.1 CADORecordBinding绑定类设计

为了便于操作数据库,系统定义了7个CADORecordBinding类的派生类(如表3-1所示),用于程序与数据库表字段的交互。这里以CDevCodeRs类为例,CDevCodeRs类用于绑定device_code表记录集,其定义如下。

例程3.1代码位置:光盘\第3章\ InstrumentManage\ dataBinding.h

    01   class CDevCodeRs:public CADORecordBinding
    02   {
    03   BEGIN_ADO_BINDING(CDevCodeRs)
    04      //绑定对应表的第一个字段
    05      ADO_VARIABLE_LENGTH_ENTRY2(1,adVarChar,m_sz_code,
    06                                    sizeof(m_sz_code),m_sts_code,TRUE)
    07       //绑定对应表的第二个字段
    08      ADO_VARIABLE_LENGTH_ENTRY2(2,adVarChar,m_sz_name,
    09                                     sizeof(m_sz_name),m_sts_name,TRUE)
    10   END_ADO_BINDING()
    11   public:
    12      ULONG m_sts_code;                   //存放字段状态
    13      ULONG m_sts_name;                   //存放字段状态
    14      CHAR  m_sz_code[10];                 //存放字段值
    15      CHAR  m_sz_name[40];                //存放字段值
    16   public:
    17       void FillFieldsArray(COleSafeArray&fields,COleSafeArray&values);//填充链表
    18   };

第3~10行代码通过使用BEGIN_ADO_BINDING宏将要绑定的字段与变量名关联起来。每个字段对应于两个变量,一个存放字段的值,另一个存放字段的状态。字段用从1开始的序号表示,如1、2、3等。

特别要注意的是,如果要绑定的字段是字符串类型,则对应的字符数组的元素个数一定要比字段长度大2(如m_sz_code[10],其绑定的字段的长度实际是8),不这样绑定就会失败。

在第17行代码定义了FillFieldsArray函数,实现将绑定记录集的字段名和对应的值添加到COleSafeArray链表中,FillFieldsArray函数的实现代码如下。

例程3.2代码位置:光盘\第3章\ InstrumentManage\ DataBinding.cpp

    01  void CDevCodeRs::FillFieldsArray(COleSafeArray&vaFieldlist,
    02                                   COleSafeArray&vaValuelist)
    03  {
    04       vaFieldlist.CreateOneDim(VT_VARIANT,2);//创建一维链表,含有两个元素
    05       long lArrayIndex[1];                               //定义一维数组
    06       lArrayIndex[0]=0;
    07       vaFieldlist.PutElement(lArrayIndex,&(_variant_t("code")));//将字段code添加链表
    08       lArrayIndex[0]=1;
    09       vaFieldlist.PutElement(lArrayIndex,&(_variant_t("name")));//将字段name添加链表
    10       vaValuelist.CreateOneDim(VT_VARIANT,2);    //创建一维链表,含有两个元素
    11       lArrayIndex[0]=0;
    12       vaValuelist.PutElement(lArrayIndex,&(_variant_t(m_sz_code)));//添加链表值
    13       lArrayIndex[0]=1;
    14       vaValuelist.PutElement(lArrayIndex,&(_variant_t(m_sz_name)));//添加链表值
    15  }

其中,第7行和第9行代码实现将device_code表的两个字段添加到链表vaFieldlist中,第12行和第14行代码实现将字段值添加到链表vaValuelist中。

其他的CADORecordBinding类派生类(参见表3-1)的创建与此类似,这里就不一一详细介绍了。

3.5.2 ADO连接数据库设计

ADO是一组动态链接库,因此在使用之前还必须导入ADO并且初始化。在头文件“stdafx.h”中,通过导入符号“#import”导入ADO库文件,如下:

    #import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF",
"adoEOF")

编译的时候系统会生成msado15.tlh、ado15.tli两个C++头文件来定义ADO库。为了避免常数冲突,将常数EOF改名为adoEOF。

导入ADO库后,就要对其进行初始化。在MFC应用里,一般在应用类的InitInstance成员函数里初始化OLE/COM库环境比较合适。初始化过程非常简单,只需简单地调用AfxOleInit函数即可,实现代码如下。

例程3.3代码位置:光盘\第3章\ InstrumentManage\ InstrumentManage.cpp

    01  BOOL CInstrumentManageApp::InitInstance()
    02  {
    03       //初始化OLE库
    04       if(!AfxOleInit())                         //初始化失败
    05       {
    06           AfxMessageBox(IDP_OLE_INIT_FAILED);
    07           return FALSE;
    08       }
    09       ……
    10  }

在工程中引入ADO对象后,就可以通过Connection对象连接数据源了。在对话框的初始化函数OnInitDialog中,创建登录对话框,实现连接数据源,代码如下。

例程3.4代码位置:光盘\第3章\ InstrumentManage\ InstrumentManageDlg.cpp

    01  BOOL CInstrumentManageDlg::OnInitDialog()
    02  {
    03       CDialog::OnInitDialog();
    04       ……
    05       //弹出用户登录对话框
    06       CDlgLogIn dlg;                              //登录对话框对象
    07       do
    08       {
    09           if(!dlg.DoModal())                       //弹出登录对话框
    10               EndDialog(0);                       //退出程序
    11       }
    12       while(dlg.m_UsrName.GetLength()==0);            //用户名不为空
    13       //登录数据库,若失败,则关闭程序。
    14       try
    15       {
    16           m_DBCnt.CreateInstance(__uuidof(Connection));  //创建Connection对象
    17           CString sql_;
    18           sql_.Format("DSN=InstrumentManage;UID=%s;PWD=%s",dlg.m_UsrName,
    19                       dlg.m_UsrPwd);             //连接字符串
    20           _bstr_t sql=sql_;
    21           m_DBCnt->Open(sql,"","",adModeUnknown);    //连接数据库
    22           m_logMngr.Setup(m_DBCnt,dlg.m_UsrName);   //添加日志
    23           m_logMngr.AddLog("登录数据库");           //添加日志操作
    24       }
    25       catch(_com_error&e)                          //捕捉异常
    26      {
    27           AfxMessageBox(e.ErrorMessage());           //弹出提示对话框
    28           this->EndDialog(0);                       //退出窗口
    29      }
    30       //采购设备位图按钮
    31       m_ctrDeVin.SetXAlign(1);                      //文本显示方式:在下
    32       m_ctrDeVin.SetFlatBack(RGB(255,255,247));        //设置为Flat时的背景色
    33       m_ctrDeVin.SetBackColor(RGB(255,255,247));       //设置背景色
    34       m_ctrDeVin.SetForeImage(IDB_BITMAP1,CSize(64,64));//设置按钮图像
    35       ……
    36       return TRUE;
    37  }

第7~12 行代码实现创建并弹出登录对话框,第16 行代码实现创建Connection对象m_DBCnt,m_DBCnt对象在头文件中声明如下:

    _ConnectionPtr m_DBCnt;                      //声明Connection对象

第21行代码通过Connection对象的Open函数连接数据库,第22、第23行代码通过调用日志管理类CLogMngr对象m_logMngr的相关函数将连接数据源操作写入操作日志表。第31~34行代码实现构造自定义带有文本的位图按钮,具体可参见CMyButton类的实现代码,

3.5.3 日志管理类CLogMngr

为了便于操作,系统开发了日志管理类CLogMngr实现向操作日志表user_operation中添加记录,CLogMngr类设计了两个成员函数:Setup和AddLog。

Setup函数实现设置连接对象和用户参数,代码如下。

例程3.5代码位置:光盘\第3章\ InstrumentManage\ LogMngr.cpp

    01       void Setup(_ConnectionPtr cnnt,CString&user)       //设置参数
    02       {
    03           m_DBCnt=cnnt;                        //Connection对象
    04           m_user=user;                           //用户
    05       }

AddLog函数实现向操作日志表user_operation中添加记录,代码如下。

例程3.6代码位置:光盘\第3章\ InstrumentManage\ LogMngr.cpp

    01  bool CLogMngr::AddLog(LPCSTR op)                 //添加日志
    02  {
    03       CTime tm=CTime::GetCurrentTime();             //获取当前时间
    04       CString sql_;
    05       //构造INSERT语句
    06       sql_.Format("INSERT INTO user_operation(do_user,do_what,do_date)
    07                   VALUES('%s','%s','%d-%d-%d %d:%d:%d')",m_user,op,
    08           tm.GetYear(),tm.GetMonth(),tm.GetDay(),      //年、月、日
    09           tm.GetHour(),tm.GetMinute(),tm.GetSecond());   //时、分、秒
    10       _bstr_t sql=sql_;
    11       try
    12       {
    13           m_DBCnt->Execute(sql,NULL,adCmdText);     //执行SQL
    14       }
    15       catch(_com_error&e)                          //捕捉异常
    16      {
    17         CString Error=e.ErrorMessage();
    18         AfxMessageBox(e.ErrorMessage());         //弹出错误提示对话框
    19           return false;
    20      }
    21       return true;
    22  }

在第1行代码,函数的参数op记录的是用户的操作说明,第3行代码获取当前时间作为用户的操作时间,在执行第13行代码时,写入数据库表中。

3.6 设备采购入库模块开发

设备采购入库模块包括设备编码、设备采购入库登记和设备采购查询三个子功能模块。

3.6.1 设备编码功能开发

设备入库或其他操作,其操作对象都是唯一的设备编码,而不是设备名。执行“设备采购入库”→“设备编码”菜单命令项(或“设备编码”按钮),会弹出如图3-4所示的“设备代码管理”对话框,在其中可以添加、修改和删除设备编码信息。

“设备代码管理”对话框对应的对话框类为CDlgDevcode,在其初始化函数中OnInitDialog中,实现初始化列表控件,代码如下。

例程3.7代码位置:光盘\第3章\ InstrumentManage\ DlgDevcode.cpp

    01  BOOL CDlgDevcode::OnInitDialog()
    02  {
    03       CDialog::OnInitDialog();
    04       m_list.InsertColumn(0,"设备编码");           //列表控件添中添加列
    05       m_list.InsertColumn(1,"设备名称");           //列表控件添中添加列
    06       RECT rect;
    07       m_list.GetWindowRect(&rect);               //获取列表控件窗口区域
    08       int wid=rect.right-rect.left;                 //区域宽度
    09       m_list.SetColumnWidth(0,wid/2);             //第一列宽度
    10       m_list.SetColumnWidth(1,wid/2);             //第二列宽度
    11       m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT);//设置风格
    12       RefreshData();                           //向列表控件中添加数据
    13       return TRUE;
    14  }

第4、第5行代码向列表控件中添加两列,而后第9、第10行代码设置各列的宽度,最后第12行代码通过RefreshData函数实现向列表控件中添加数据。RefreshData函数代码如下。

例程3.8代码位置:光盘\第3章\ InstrumentManage\ DlgDevcode.cpp

    01  void CDlgDevcode::RefreshData()             //向列表控件中添加数据
    02  {
    03       m_list.DeleteAllItems();                    //清空列表控件
    04       m_list.SetRedraw(FALSE);
    05       //读取DEVICE_CODE表中数据
    06       _bstr_t strSQL("SELECT*FROM DEVICE_CODE");
    07       _RecordsetPtr MySet;                      //Recordset对象
    08       int i=0;
    09       try
    10       {
    11           MySet.CreateInstance(__uuidof(Recordset));//创建记录集对象
    12           MySet=m_DBCnt->Execute(strSQL,NULL,adCmdText);    //执行查询SQL
    13           _variant_t Holder;
    14           while(!MySet->adoEOF)               //遍历记录集
    15          {
    16              Holder=MySet->GetCollect("code");   //code字段记录
    17               if(Holder.vt!=VT_NULL)
    18                   m_list.InsertItem(i,(char*)(_bstr_t)Holder);
    19              Holder=MySet->GetCollect("name");  //name字段记录
    20               if(Holder.vt!=VT_NULL)
    21                   m_list.SetItemText(i,1,(char*)(_bstr_t)Holder);
    22               MySet->MoveNext();         //下一记录
    23           }
    24       }
    25       catch(_com_error&e)                     //捕捉异常
    26      {
    27         AfxMessageBox(e.ErrorMessage());
    28           m_list.SetRedraw(TRUE);              //重绘列表控件
    29           return;
    30      }
    31       m_list.SetRedraw(TRUE);                  //重绘列表控件
    32  }

第12行代码执行SELECT查询语句得到DEVICE_CODE表中的所有记录集,第14~23行代码实现遍历记录集,分别提取记录相应字段的值添加到列表控件中。

在“设备代码管理”对话框中,要添加设备编码,只需在编辑框中输入设备名称和设备编码,单击“新增”按钮即可。“新增”按钮响应函数OnBtnDcadd的代码如下。

例程3.9代码位置:光盘\第3章\ InstrumentManage\ DlgDevcode.cpp

    01  void CDlgDevcode::OnBtnDcadd()
    02  {
    03       UpdateData();                                   //获取控件数据
    04       CString sql_;
    05       sql_.Format("INSERT INTO DEVICE_CODE(code,name)   //构造INSERT语句
    06                   VALUES('%s','%s')",m_code,m_name);
    07       _bstr_t sql=sql_;
    08       try
    09       {
    10           m_DBCnt->Execute(sql,NULL,adCmdText);     //执行SQL INSERT语句
    11       }
    12       catch(_com_error&e)                              //捕捉异常
    13      {
    14         AfxMessageBox(e.ErrorMessage());
    15           return;
    16      }
    17       m_log->AddLog("添加设备编码记录");                //添加日志
    18       RefreshData();                               //更新列表控件
    19  }

第5、第6 行代码构造了INSERT语句,实现向DEVICE_CODE表中添加记录,第10行代码通过_ConnectionPtr对象执行该语句,实现对数据库表的操作,第17行代码实现向操作日志表中添加操作信息。

其他的删除和修改记录的实现与此类似。

3.6.2 设备采购入库登记功能开发

采购设备入库,只需执行“设备采购入库”→“设备采购”菜单命令项(或单击“采购设备”按钮),会弹出如图3-5所示的“设备购买登记”对话框。在对话框中输入采购设备信息,单击“确认”按钮,即实现了设备的采购入库操作。

“设备购买登记”对话框对应的对话框类为CDlgDevIn,“确认”按钮响应函数OnBtnDiadd的代码如下。

例程3.10代码位置:光盘\第3章\ InstrumentManage\ DlgDevIn.cpp

    01  void CDlgDevIn::OnBtnDiadd()
    02  {
    03       if(m_devs.GetCurSel()==CB_ERR)                    //入库设备为空
    04       {
    05           AfxMessageBox("请选择采购设备");
    06           return;                                    //返回
    07       }
    08       if(!UpdateData())                                //获取用户输入
    09           return;
    10       _RecordsetPtr pRst=NULL;                         //记录集对象
    11       CDevInRs rs;                                //ADORecordBinding对象
    12       try
    13       {
    14           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));
    15           //打开device_in表
    16           pRst->Open("device_in",_variant_t((IDispatch*)m_DBCnt,true),
    17                       adOpenKeyset,adLockOptimistic,adCmdTable);
    18           m_devs.GetWindowText(rs.m_sz_code,11);          //获取设备编码
    19           sprintf(rs.m_sz_date,"%d-%d-%d %d:%d:%d",        //入库时间
    20               m_date.GetYear(),m_date.GetMonth(),m_date.GetDay(),
    21               m_time.GetHour(),m_time.GetMinute(),m_time.GetSecond());
    22           strcpy(rs.m_sz_provider,m_provider);              //供应商
    23           strcpy(rs.m_sz_tel,m_tel);                      //供应商电话
    24           rs.m_f_number=m_number;                     //入库数量
    25           rs.m_f_price=m_price;                        //单价
    26           strcpy(rs.m_sz_buyer,m_buyer);                  //购买人
    27           COleSafeArray vaFieldlist,vaValuelist;             //链表对象
    28           rs.FillFieldsArray(vaFieldlist,vaValuelist);           //填充字段链表
    29           TESTHR(pRst->AddNew(vaFieldlist,vaValuelist));    //添加记录
    30           pRst->Close();                               //关闭记录集
    31           CString sql_;
    32           //构造从device_info表查询该设备编码的库存信息的SQL语句
    33           sql_.Format("SELECT*FROM device_info WHERE code='%s'",
    34                       rs.m_sz_code);
    35           _bstr_t sql=sql_;
    36           //执行查询,得到记录集
    37           pRst->Open(sql,_variant_t((IDispatch*)m_DBCnt,true),
    38                           adOpenKeyset,adLockOptimistic,adCmdText);
    39           if(pRst->GetRecordCount()==0)      //查询结果为空,即该设备库存不存在
    40           {
    41               CDevRs rsDev; //ADORecordBinding对象,与device_info字段关联
    42               strcpy(rsDev.m_sz_code,rs.m_sz_code);    //设备编码
    43               rsDev.m_f_cur=rs.m_f_number;         //现库存数量
    44               rsDev.m_f_total=rs.m_f_number;        //总数量
    45               rsDev.m_f_max=25;                  //最大数量
    46               rsDev.m_f_min=2;                   //最小数量
    47               COleSafeArray vaFields,vaValues;        //链表对象
    48               rsDev.FillFieldsArray(vaFields,vaValues);   //填充字段链表
    49               TESTHR(pRst->AddNew(vaFields,vaValues));//添加记录
    50           }
    51           else                          //该设备在库存中已存在
    52           {
    53               CDevRs rsDev; //ADORecordBinding对象,与device_info字段关联
    54               IADORecordBinding   *picRs=NULL;   //指针对象
    55               TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),
    56                       (LPVOID*)&picRs));
    57               TESTHR(picRs->BindToRecordset(&rsDev));//绑定记录集
    58               strcpy(rsDev.m_sz_code,rs.m_sz_code);    //设备编码
    59               rsDev.m_f_cur=rs.m_f_number+rsDev.m_f_cur;//库存数量增加
    60               rsDev.m_f_total=rs.m_f_number+rsDev.m_f_total;//总数量增加
    61               TESTHR(picRs->Update(&rsDev));       //更新记录
    62               picRs->Release();                    //释放对象
    63           }
    64           pRst->Close();                           //关闭记录集
    65       }
    66       catch(_com_error&e)                          //捕捉异常
    67       {
    68           AfxMessageBox(e.ErrorMessage());
    69           return;
    70       }
    71       MessageBox("完成操作!");
    72       m_log->AddLog("添加入库信息");                //添加操作日志
    73       EndDialog(0);                               //退出对话框窗口
    74  }

第3~9 行代码,获取用户的输入,判断输入的有效性,第14~30 行代码通过ADORecordBinding对象将用户输入的采购信息添加到表device_in中,第31~65行代码实现的功能就是更新库存设备信息表。如果在库存设备中,该入库设备不存在,就在device_info表中添加该设备记录,如果该设备已经存在,就更新设备的相关数量字段记录。添加成功,在第72行代码将添加操作写入操作日志表中。

另外,函数中多次用到的TESTHR函数,其在头文件“stdafx.h”中定义如下:

    inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};

3.6.3 设备采购查询管理功能开发

操作员可以查询、修改设备的采购入库登记信息。执行“设备采购入库”→“采购查询”菜单命令项(或单击“采购查询”按钮),会弹出如图3-6所示的设备入库信息查询对话框。

在对话框的列表控件中,列出了所有设备采购入库登记信息,选择列表项,在编辑框中分别列出各字段值。操作员可以在编辑框中修改相关信息,单击“修改”按钮,即实现了修改设备的采购入库登记信息。

设备入库信息查询对话框对应的对话框类为CDlgViewDevIn,“修改”按钮响应函数OnBtnViupdate的代码如下。

例程3.11代码位置:光盘\第3章\ InstrumentManage\ DlgViewDevIn.cpp

    01  void CDlgViewDevIn::OnBtnViupdate()
    02  {
    03       if(!UpdateData())                                //获取编辑框数据
    04           return;
    05       CString sql_;
    06       //从device_in表中根据入库时间查询记录
    07       sql_.Format("SELECT*FROM device_in WHERE in_date='%s'",m_date);
    08       _bstr_t sql=sql_;
    09       _RecordsetPtr pRst=NULL;                         //记录集对象
    10       IADORecordBinding   *picRs=NULL;                //指针对象
    11       CDevInRs rs;                           //ADORecordBinding对象
    12       try
    13       {
    14           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));   //创建记录集对象
    15           //执行查询操作
    16           pRst->Open(sql,_variant_t((IDispatch*)m_DBCnt,true),
    17                       adOpenKeyset,adLockOptimistic,adCmdText);
    18           TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),
    19                       (LPVOID*)&picRs));
    20           TESTHR(picRs->BindToRecordset(&rs));           //绑定记录集
    21           strcpy(rs.m_sz_provider,m_provider);              //供应商
    22           strcpy(rs.m_sz_tel,m_tel);                      //供应商电话
    23           rs.m_f_price=m_price;                        //单价
    24           strcpy(rs.m_sz_buyer,m_buyer);                  //购买人
    25           TESTHR(picRs->Update(&rs));                   //更新记录
    26           picRs->Release();                            //释放对象
    27           pRst->Close();                               //关闭记录集
    28       }
    29       catch(_com_error&e)                              //捕捉异常
    30       {
    31         AfxMessageBox(e.ErrorMessage());
    32           return;
    33      }
    34       MessageBox("完成操作!");
    35       m_log->AddLog("修改入库信息");                    //添加操作日志
    36       RefreshData();                                   //更新列表控件数据
    37  }

第7行代码构造SQL查询语句,根据入库时间字段查询device_in表中对应的记录,在第16行代码执行该语句,得到查询记录集,第20行代码将CDevInRs对象绑定记录集,然后修改相关字段,在第25 行代码执行更新操作。第36 行代码RefreshData函数实现读取device_in表中的所有记录并添加到列表控件中,具体实现与3.6.1节介绍的CDlgDevcode类的RefreshData函数类似。

3.7 借还设备管理模块开发

借还设备管理模块包括库存管理、借用设备、归还设备,以及借用、归还设备信息查询五个子功能模块。

3.7.1 库存管理功能开发

操作员可以通过库存管理模块查询库存设备信息,并能够进行库存盘点操作。执行“借还设备管理”→“库存信息”菜单命令项(或“库存设备”按钮),会弹出如图3-15所示“库存管理”对话框。

图3-15 “库存管理”对话框

在对话框中列出了所有库存设备的数量信息。在类表框中单击记录,就可以在下面的编辑框中修改相关字段,单击“修改”按钮,即可修改记录。“库存管理”对话框对应的对话框类为CDlgDev,“修改”按钮响应函数OnBtnDevupd的代码如下。

例程3.12代码位置:光盘\第3章\ InstrumentManage\ DlgDev.cpp

    01  void CDlgDev::OnBtnDevupd()
    02  {
    03       UpdateData();                           //获取控件数据
    04       _RecordsetPtr pRst=NULL;
    05       IADORecordBinding   *picRs=NULL;        //接口指针
    06       CDevRs rs;
    07       m_devs.GetWindowText(rs.m_sz_code,10);      //获取设备编码
    08       if(strlen(rs.m_sz_code)<=0)                 //编码为空
    09       {
    10           MessageBox("请选择一个设备");
    11           return;                                //返回
    12       }
    13       CString sql_;
    14       //构造SQL查询语句
    15       sql_.Format("SELECT*FROM DEVICE_INFO WHERE code='%s'",
    16                       rs.m_sz_code);
    17       _bstr_t sql=sql_;
    18       try
    19       {
    20           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));   //创建记录集
    21           //执行查询操作
    22           pRst->Open(sql,_variant_t((IDispatch*)m_DBCnt,true),adOpenKeyset,
    23                       adLockOptimistic,adCmdText);
    24           TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),
    25                       (LPVOID*)&picRs));
    26           TESTHR(picRs->BindToRecordset(&rs));           //绑定记录集
    27           rs.m_f_cur=m_cur;                           //现有库存
    28           rs.m_f_max=m_max;                         //库存上限
    29           rs.m_f_min=m_min;                          //库存下限
    30           rs.m_f_total=m_total;                     //装备总数
    31           TESTHR(picRs->Update(&rs));                   //更新
    32           picRs->Release();                            //释放对象
    33           pRst->Close();                               //关闭记录集
    34       }
    35       catch(_com_error&e)                              //捕捉异常
    36       {
    37         AfxMessageBox(e.ErrorMessage());
    38           return;
    39      }
    40       MessageBox("完成操作!");
    41       m_log->AddLog("修改库存信息");                    //添加操作日志
    42       RefreshData();                                   //更新列表控件数据
    43  }

函数根据设备编码获取DEVICE_INFO表中的相应记录,根据用户的输入,通过绑定记录集对象CDevRs更新记录,具体操作过程与3.6.3节介绍的OnBtnViupdate函数基本相同。

3.7.2 借用设备功能开发

有用户来借用设备,操作员只需执行“借还设备管理”→“借用设备”菜单命令项(或单击“借用设备”按钮),会弹出如图3-16所示的“设备借用登记”对话框。

图3-16 设备借用登记对话框

在对话框中输入相关的借用设备信息,单击“确认”按钮,即实现了设备的借用登记操作。设备借用登记对话框对应的对话框类为CDlgDevOut,“确认”按钮响应函数OnBtnDoadd的代码如下。

例程3.13代码位置:光盘\第3章\ InstrumentManage\ DlgDevOut.cpp

    01  void CDlgDevOut::OnBtnDoadd()
    02  {
    03       if(m_devs.GetCurSel()==CB_ERR)               //设备编码为空
    04       {
    05           MessageBox("请选择一个设备");         //提示对话框
    06           return;                                //返回
    07       }
    08       UpdateData();                               //获取控件数据
    09       if((m_max>0)&&(m_number>m_max))            //借用数量超过库存
    10       {
    11           MessageBox("超过最大限量!");
    12           return;                                //返回
    13       }
    14       _RecordsetPtr pRst=NULL;                     //记录集对象
    15       CDevOutRs rs;                              //绑定类对象
    16       try
    17       {
    18           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));//创建记录集
    19           //打开借用设备登记表device_out
    20           pRst->Open("device_out",_variant_t((IDispatch*)m_DBCnt,true),
    21                       adOpenKeyset,adLockOptimistic,adCmdTable);
    22           m_devs.GetWindowText(rs.m_sz_code,11);      //设备编码
    23           ……                     //获取其他记录,赋予rs的相关字段
    24           COleSafeArray vaFieldlist,vaValuelist;
    25           rs.FillFieldsArray(vaFieldlist,vaValuelist);       //填充字段链表
    26           TESTHR(pRst->AddNew(vaFieldlist,vaValuelist));//添加记录
    27           pRst->Close();                           //关闭记录集
    28           CString sql_;
    29           //从库存信息表DEVICE_INFO查询该设备记录
    30           sql_.Format("SELECT*FROM DEVICE_INFO WHERE code='%s'",
    31                       rs.m_sz_code);
    32           _bstr_t sql=sql_;
    33           //执行查询
    34           pRst->Open(sql,_variant_t((IDispatch*)m_DBCnt,true),
    35                       adOpenKeyset,adLockOptimistic,adCmdText);
    36           if(pRst->GetRecordCount()==1)                  //结果不为空
    37           {
    38               CDevRs rsDev;//绑定类对象
    39               IADORecordBinding   *picRs=NULL;        //接口指针
    40               TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),
    41                                       (LPVOID*)&picRs));
    42               TESTHR(picRs->BindToRecordset(&rsDev));    //绑定记录集
    43               strcpy(rsDev.m_sz_code,rs.m_sz_code);        //设备编码
    44               rsDev.m_f_cur=rsDev.m_f_cur-rs.m_f_number;//库存数量减去相应值
    45               TESTHR(picRs->Update(&rsDev));            //更新数据库
    46               picRs->Release();                        //释放对象
    47           }
    48           pRst->Close();                               //关闭记录集
    49       }
    50       catch(_com_error&e)                              //捕捉异常
    51       {
    52         AfxMessageBox(e.ErrorMessage());
    53           return;                                    //返回
    54      }
    55       MessageBox("借用设备完成操作!");
    56       m_log->AddLog("添加出库信息");                    //添加操作日志
    57       EndDialog(0);                                   //关闭对话框
    58  }

函数首先判断用户输入的有效性,其中第9~13行代码用于判断用户借用的数量是否大于库存设备的数量;第18~27行代码将设备的借用登记信息添加到表device_out中;第28~48行代码实现更新库存信息表DEVICE_INFO中该装备的库存数量,其实现过程与3.6.2节介绍的设备入库登记操作流程基本相同。

3.7.3 归还设备功能开发

有用户来归还设备,操作员只需执行“借还设备管理”→“归还设备”菜单命令项(或单击“归还设备”按钮),会弹出如图3-17所示的“设备还库登记”对话框。

图3-17 “设备还库登记”对话框

在对话框中输入相关的设备还库信息,单击“确认”按钮,即实现了设备的归还登记操作。设备还库登记对话框对应的对话框类为CDlgDevRet,“确认”按钮响应函数OnBtnDradd的代码如下。

例程3.14代码位置:光盘\第3章\ InstrumentManage\ DlgDevRet.cpp

    01  void CDlgDevRet::OnBtnDradd()
    02  {
    03       if(m_devs.GetCurSel()==CB_ERR)               //设备编码为空
    04       {
    05           MessageBox("请选择一个设备");
    06           return;                                //返回
    07       }
    08       UpdateData();                               //获取控件输入
    09       _RecordsetPtr pRst=NULL;                     //记录集对象
    10       CDevRetRs rs;                               //绑定类对象
    11       try
    12       {
    13           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));//创建记录集
    14           //打开device_return表
    15           pRst->Open("device_return",_variant_t((IDispatch*)m_DBCnt,true),
    16                       adOpenKeyset,adLockOptimistic,adCmdTable);
    17           m_devs.GetWindowText(rs.m_sz_code,11);      //获取设备编码
    18           ……                     //获取其他记录,赋予rs的相关字段
    19           COleSafeArray vaFieldlist,vaValuelist;         //字段链表
    20           rs.FillFieldsArray(vaFieldlist,vaValuelist);       //填充链表
    21           TESTHR(pRst->AddNew(vaFieldlist,vaValuelist));//添加记录
    22           pRst->Close();                           //关闭记录集
    23           CString sql_;
    24           //从device_info表中查询该设备记录
    25           sql_.Format("SELECT*FROM device_info WHERE code='%s'",
    26                       rs.m_sz_code);
    27           _bstr_t sql=sql_;
    28           //执行查询SQL
    29           pRst->Open(sql,_variant_t((IDispatch*)m_DBCnt,true),
    30                       adOpenKeyset,adLockOptimistic,adCmdText);
    31           if(pRst->GetRecordCount()==1)              //查询结果不为空
    32           {
    33               CDevRs rsDev;                      //绑定类对象
    34               IADORecordBinding   *picRs=NULL;   //接口指针
    35               TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),
    36                           (LPVOID*)&picRs));
    37               TESTHR(picRs->BindToRecordset(&rsDev));//绑定记录集
    38               strcpy(rsDev.m_sz_code,rs.m_sz_code);    //获取设备编码
    39               rsDev.m_f_cur=rs.m_f_number+rsDev.m_f_cur;//库存数量增加
    40               TESTHR(picRs->Update(&rsDev));       //更新记录
    41               picRs->Release();                    //释放对象
    42           }
    43           pRst->Close();                           //关闭记录集
    44       }
    45       catch(_com_error&e)                          //捕捉异常
    46       {
    47         AfxMessageBox(e.ErrorMessage());
    48           return;                                //返回
    49      }
    50       MessageBox("完成归还操作!");
    51       m_log->AddLog("添加还库信息");                //添加操作日志
    52       EndDialog(0);                               //关闭对话框
    53  }

函数代码的操作流程与上节介绍的借用设备相似,即先将还库设备信息添加到还库登记表device_return中,然后更新库存信息表device_info中相应设备的库存数量。

3.7.4 归还设备查询管理功能开发

在借还设备管理模块中,操作员还可以查询、修改设备的借用登记和还库登记信息。只需执行“借还设备管理”→“借用查询”菜单命令项(或单击“借用查询”按钮),系统会弹出如图3-18所示的“查看出库信息”对话框。

图3-18 “查看出库信息”对话框

在对话框列表控件中,列出了所有的外借设备的信息。在列表控件中双击记录,该记录相关字段的值就会在对话框下面的编辑框中显示,管理员可以对其进行修改和删除操作。以修改操作为例,“修改”按钮在对话框类CDlgViewOut中的响应函数为OnBtnVoupd,其实现代码如下。

例程3.15代码位置:光盘\第3章\ InstrumentManage\ DlgViewOut.cpp

    01  void CDlgViewOut::OnBtnVoupd()
    02  {
    03       if(!UpdateData())                                //获取控件输入
    04           return;
    05       CString sql_;
    06       //根据出库时间从device_out表中查询记录
    07       sql_.Format("SELECT*FROM device_out WHERE out_date='%s'",m_date);
    08       _bstr_t sql=sql_;
    09       _RecordsetPtr pRst=NULL;                         //Recordse对象指针
    10       IADORecordBinding   *picRs=NULL;                //接口指针
    11       CDevOutRs rs;                              //ADORecordBinding对象
    12       try
    13       {
    14           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));   //创建记录集对象
    15           pRst->Open(sql,_variant_t((IDispatch*)m_DBCnt,true),adOpenKeyset,
    16                       adLockOptimistic,adCmdText);      //执行查询
    17           TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),
    18                                   (LPVOID*)&picRs));
    19           TESTHR(picRs->BindToRecordset(&rs));       //绑定记录集
    20           strcpy(rs.m_sz_code,m_code);                //设备编号
    21           strcpy(rs.m_sz_dept,m_dept);                //使用部门
    22           strcpy(rs.m_sz_date,m_date);                //出库时间
    23           strcpy(rs.m_sz_keeper,m_keeper);            //经手人
    24           rs.m_f_number=m_number;                //出库数量
    25           strcpy(rs.m_sz_taker,m_taker);               //领取人
    26           strcpy(rs.m_sz_usage,m_usage);              //用途
    27           TESTHR(picRs->Update(&rs));              //更新数据库记录
    28           picRs->Release();                        //释放对象
    29           pRst->Close();                           //关闭记录集
    30       }
    31       catch(_com_error&e)                          //捕捉异常
    32       {
    33       AfxMessageBox(e.ErrorMessage());
    34           return;                                //返回
    35      }
    36       MessageBox("完成操作!");
    37       m_log->AddLog("修改出库信息");                //添加到操作日志
    38       RefreshData();                               //更新显示列表控件数据
    39  }

第7行代码构造SQL查询语句,根据出库时间字段查询device_out表中对应的记录,在第15行代码执行该语句,得到查询记录集,第19行代码将CDevOutRs对象绑定记录集,然后修改相关字段,在第27 行代码执行更新操作。第38 行代码RefreshData函数实现读取device_out表中的所有记录并添加到列表控件中,具体实现与3.6.1节介绍的CDlgDevcode类的RefreshData函数类似。

此外,管理员也可以对设备的还库信息进行查询、管理。执行“借还设备管理”→“归还查询”菜单命令项(或单击“归还查询”按钮),系统会弹出如图3-19所示的“查看还库信息”对话框。

图3-19 “查看还库信息”对话框

在对话框的列表控件中,显示了所有的还库设备信息,管理员可以对还库信息进行修改、删除操作。具体实现过程与外借设备的查询管理功能基本相同,只是操作的数据库表不同,这里就不再详细介绍。

3.8 设备需求统计模块开发

设备需求统计模块包括库存报警、设备预约、采购报表,以及预约查询、报表查询五个子功能模块。

3.8.1 设备预约功能开发

用户可以预约设备,操作员只需执行“设备需求统计”→“设备预约”菜单命令项(或单击“设备预约”按钮),会弹出如图3-20所示的“设备需求登记”对话框。

图3-20 “设备需求登记”对话框

在对话框中输入预约设备信息,单击“确认”按钮,即实现了设备的预约操作。“设备需求登记”对话框对应的对话框类为CDlgDevNeed,“确认”按钮响应函数OnBtnDnadd的代码如下。

例程3.16代码位置:光盘\第3章\ InstrumentManage\ DlgDevNeed.cpp

    01  void CDlgDevNeed::OnBtnDnadd()
    02  {
    03       if(m_devs.GetCurSel()==CB_ERR)                    //设备编码为空
    04       {
    05           MessageBox("请选择一个设备");
    06           return;                                    //返回
    07       }
    08       UpdateData();                                   //获取控件输入
    09       if(m_dept.GetLength()<=0)                     //使用部门编辑框为空
    10       {
    11           MessageBox("部门一项不能为空!");
    12           return;                                    //返回
    13       }
    14       _RecordsetPtr pRst=NULL;                         //记录集对象
    15       CDevNeedRs rs;                                 //绑定类对象
    16       try
    17       {
    18           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));   //创建记录集
    19           //打开device_need表
    20           pRst->Open("device_need",_variant_t((IDispatch*)m_DBCnt,true),
    21                       adOpenKeyset,adLockOptimistic,adCmdTable);
    22           m_devs.GetWindowText(rs.m_sz_code,11);  //获取设备编码,添加到rs对象
    23           ……                         //获取其他记录,赋予rs的相关字段
    24           COleSafeArray vaFieldlist,vaValuelist;             //字段链表对象
    25           rs.FillFieldsArray(vaFieldlist,vaValuelist);           //填充链表
    26           TESTHR(pRst->AddNew(vaFieldlist,vaValuelist));    //添加记录
    27           pRst->Close();                               //关闭记录集
    28       }
    29       catch(_com_error&e)                              //捕捉异常
    30       {
    31         AfxMessageBox(e.ErrorMessage());
    32           return;                                    //返回
    33      }
    34       MessageBox("完成操作!");
    35       m_log->AddLog("添加需求信息");                    //添加操作日志
    36       EndDialog(0);                                   //关闭对话框
    37  }

函数首先获取对话框控件信息,通过ADO绑定记录类CDevNeedRs对象将预约信息添加到表device_need中。

3.8.2 采购报表功能开发

仓库管理员可以根据用户的需求,以及设备的库存状况指定相应的设备采购报表。制定采购报表只需执行“设备需求统计”→“采购报表”菜单命令项(或单击“采购报表”按钮),会弹出如图3-21所示的设备采购计划报表对话框。

图3-21 设备采购计划报表对话框

当用户从设备编号组合框中选择设备编号后,其对应的设备名称、最大库存、总库存和现有库存数量等信息就会自动添加到相应的编辑框中。为了实现该功能,在对话框类CDlgReport中为组合框控件添加CBN_CLOSEUP消息响应函数OnCloseupComboRpdevs,函数代码如下。

例程3.17代码位置:光盘\第3章\ InstrumentManage\ DlgReport.cpp

    01  void CDlgReport::OnCloseupComboRpdevs()
    02  {
    03       char buf[64];
    04       m_devs.GetWindowText(buf,64);             //获取组合框中设备编号
    05       if(strlen(buf)<=0)                        //没有选中设备编号
    06           return;                            //返回
    07       _RecordsetPtr pRst=NULL;                //记录集对象
    08       IADORecordBinding   *picRs=NULL;        //接口指针
    09       CDevCodeRs rs1;                        //绑定类对象
    10       CDevRs rs2;                            //绑定类对象
    11       _bstr_t strSQL;
    12       try
    13       {
    14           CString sql;
    15           //从DEVICE_CODE表根据设备编码查询设备名称
    16           sql.Format("SELECT*FROM DEVICE_CODE WHERE code='%s'",buf);
    17           strSQL=sql;
    18           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));   //创建记录集对象
    19           pRst=m_DBCnt->Execute(strSQL,NULL,adCmdText);//执行查询操作
    20           TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),
    21                                   (LPVOID*)&picRs));
    22           TESTHR(picRs->BindToRecordset(&rs1));          //绑定记录集
    23           m_name.Format("%s",rs1.m_sz_name);             //获取设备名称
    24           picRs->Release();                            //释放对象
    25           pRst->Close();                               //关闭记录集
    26       }
    27       catch(_com_error&e)                              //捕捉异常
    28       {
    29         AfxMessageBox(e.ErrorMessage());
    30      }
    31       try
    32       {
    33           CString sql;
    34           //从DEVICE_INFO表根据设备编码查询设备的库存信息
    35           sql.Format("SELECT*FROM DEVICE_INFO WHERE code='%s'",buf);
    36           strSQL=sql;
    37           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));   //创建记录集对象
    38           //执行SQL查询语句
    39           pRst->Open(strSQL,_variant_t((IDispatch*)m_DBCnt,true),
    40                        adOpenKeyset,adLockOptimistic,adCmdText);
    41           if(pRst->GetRecordCount()!=1)                  //该设备库存不存在
    42           {
    43               m_max=25;                            //设置最大库存
    44               m_cur=0;                             //现有数量
    45               m_total=0;                            //设备总数
    46           }
    47           else                                      //有库存
    48           {
    49               TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),
    50                       (LPVOID*)&picRs));
    51               TESTHR(picRs->BindToRecordset(&rs2));
    52               m_max=rs2.m_f_max;                    //获取最大库存信息
    53               m_cur=rs2.m_f_cur;                      //获取现有库存
    54               m_total=rs2.m_f_total;                    //获取库存总数
    55               picRs->Release();                        //释放对象
    56           }
    57           pRst->Close();                               //关闭记录集
    58       }
    59       catch(_com_error&e)                              //捕捉异常
    60       {
    61         AfxMessageBox(e.ErrorMessage());
    62      }
    63       UpdateData(FALSE);                              //显示控件数据
    64  }

函数的第14~25行代码实现根据组合框中选择的设备编码,从DEVICE_CODE表查询对应的设备名称;第33~58行代码实现根据组合框中选择的设备编码,从DEVICE_INFO表中查询设备的库存信息,最后在相应的编辑框中显示。

在对话框中输入设备采购信息后,单击“确认”按钮,即实现了生成采购报表。其具体数据操作就是将对话框中设定的控件值添加到device_wantbuy表中对应的字段中,实现方法可参考3.8.1节介绍的设备预约信息的添加操作。

3.8.3 库存报警功能开发

系统提供了库存报警功能,即仓库管理员可以方便地查询库存过多或过少的设备信息。管理员只需执行“设备需求统计”→“库存报警”菜单命令项(或单击“库存报警”按钮),会弹出如图3-22所示的设备库存报警对话框。

图3-22 设备库存报警对话框

在库存报警对话框中,可以搜索过多库存、过少库存,以及二者兼有的设备信息。以搜索过少库存为例,其按钮响应函数SearchBelow的实现代码如下。

例程3.18代码位置:光盘\第3章\ InstrumentManage\ DlgDevAlert.cpp

    01  void CDlgDevAlert::SearchBelow()
    02  {
    03       _RecordsetPtr pRst=NULL;                     //声明记录集对象
    04       IADORecordBinding   *picRs=NULL;            //接口指针
    05       CDevRs rs;                             //绑定类对象
    06       try
    07       {
    08           //构造从DEVICE_INFO表中查询库存数量少于库存下限的记录
    09           _bstr_t strSQL("SELECT*FROM DEVICE_INFO
    10                       WHERE now_number<low_number");
    11           TESTHR(pRst.CreateInstance(__uuidof(Recordset)));//创建记录集对象
    12           pRst=m_DBCnt->Execute(strSQL,NULL,adCmdText);//执行SQL
    13           TESTHR(pRst->QueryInterface(__uuidof(IADORecordBinding),
    14                                   (LPVOID*)&picRs));
    15           TESTHR(picRs->BindToRecordset(&rs));           //绑定记录集
    16           int i=0;
    17           char buf[64];
    18           while(!pRst->adoEOF)                        //遍历记录集
    19           {
    20               m_list.InsertItem(0,rs.m_sz_code);            //添加设备编码
    21               sprintf(buf,"%d",(int)rs.m_f_cur);             //现库存数量
    22               m_list.SetItemText(i,1,buf);                //添加列表列值
    23               sprintf(buf,"%d",(int)rs.m_f_max);            //库存上限
    24               m_list.SetItemText(i,2,buf);                //添加列表列值
    25               sprintf(buf,"%d",(int)rs.m_f_min);         //库存下限
    26               m_list.SetItemText(i,3,buf);                //添加列表列值
    27               sprintf(buf,"%d",(int)rs.m_f_total);            //设备总数量
    28               m_list.SetItemText(i,4,buf);                //添加列表列值
    29               pRst->MoveNext();                       //下一记录
    30           }
    31           picRs->Release();                            //释放对象
    32           pRst->Close();                               //关闭记录集
    33       }
    34       catch(_com_error&e)                              //捕捉异常
    35       {
    36         AfxMessageBox(e.ErrorMessage());
    37           return;                                    //返回
    38      }
    39  }

所谓过少库存是指设备的现有库存少于设定的库存下限。因此,函数的第9行代码构造了SQL语句,实现从DEVICE_INFO表中查询库存数量少于库存下限的记录。在第12行代码执行该SQL语句,第15行代码将绑定类CDevRs对象绑定查询记录集,在第18~30行代码依次将绑定记录集中的字段值添加到列表控件中显示。

3.8.4 设备预约查询管理功能开发

管理员可以随时对设备的预约登记进行查询、管理。只需执行“设备需求统计”→“预约查询”菜单命令项(或单击“预约查询”按钮),会弹出如图3-23所示的“查看需求信息”对话框。

图3-23 “查看需求信息”对话框

在对话框的列表控件中,显示了所有的设备预约信息。单击列表记录,该记录的相关字段值就会显示在对话框中对应的编辑框中,单击“删除”按钮,即实现了设备预约的删除操作。

“删除”按钮在对话框类CDlgViewNeed中的响应函数为OnBtnVndel,其实现代码如下。

例程3.19 代码位置:光盘\第3 章\InstrumentManage\ DlgViewNeed.cpp

    01  void CDlgViewNeed::OnBtnVndel()
    02  {
    03       CString sql_;
    04       //根据设备编号和需求部门的值从DEVICE_NEED表中删除记录
    05       sql_.Format("DELETE FROM DEVICE_NEED WHERE code='%s'AND
    06                                   department='%s'",m_code,m_dept);
    07       _bstr_t sql=sql_;
    08       try
    09       {
    10           m_DBCnt->Execute(sql,NULL,adCmdText);    //执行Delete语句
    11       }
    12       catch(_com_error&e)                          //捕捉异常
    13       {
    14         AfxMessageBox(e.ErrorMessage());
    15           return;                                //返回
    16      }
    17       MessageBox("完成操作!");
    18       m_log->AddLog("删除需求信息");                //添加操作日志
    19       RefreshData();                               //更新显示列表控件
    20  }

在函数的第5行代码构造根据设备编号和需求部门的值从Device_Need表中删除记录的SQL语句,在第10行代码实现在数据库中执行SQL语句,实现删除记录。

同样,管理员也可以对设备的采购计划报表进行查询、管理。只需执行“设备需求统计”→“报表查询”菜单命令项(或单击“报表查询”按钮),会弹出如图3-24所示的“查看设备采购计划报表”对话框。

图3-24 “查看设备采购计划报表”对话框

在该对话框中,可以查看所有的采购计划报表,并可以对报表进行删除操作。具体实现过程与前面介绍的设备预约查询管理完全相同,只是操作的数据库表不同而已。

3.9 系统管理功能开发

由于本系统只针对设备管理员,因此系统没有进行严格的身份验证和权限控制,为了维护系统的安全性,这里设置了日志管理操作。用户的登录和所有对数据库数据的更新操作就写入了日志,管理员可以通过对操作日志的查询,随时掌握系统数据的操作。

在系统中执行“系统管理”→“操作日志”菜单命令项(或单击“操作日志”按钮),会弹出如图3-25所示的“查看操作日志”对话框。

图3-25 “查看操作日志”对话框

在对话框的列表控件中,显示了所有的操作日志信息。该功能是在对话框类CDlgViewLog的初始化函数OnInitDialog中实现的,代码如下。

例程3.20代码位置:光盘\第3章\ InstrumentManage\ DlgViewLog.cpp

    01  BOOL CDlgViewLog::OnInitDialog()
    02  {
    03       CDialog::OnInitDialog();               //调用基类的初始化函数
    04       m_list.InsertColumn(0,"操作员");         //列表控件插入列
    05       m_list.InsertColumn(1,"操作日期");       //列表控件插入列
    06       m_list.InsertColumn(2,"操作内容");       //列表控件插入列
    07       RECT rect;
    08       m_list.GetWindowRect(&rect);           //获取列表控件的区域
    09       int wid=rect.right-rect.left;            //列表控件宽度
    10       m_list.SetColumnWidth(0,wid/3);         //设置列的宽度
    11       m_list.SetColumnWidth(1,wid/3);         //设置列的宽度
    12       m_list.SetColumnWidth(2,wid/3);         //设置列的宽度
    13       m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT);//设置列表控件风格
    14       RefreshData();                      //向列表控件中添加记录
    15       return TRUE;
    16  }

在函数中,首先设置列表控件和插入列,然后在第14行代码调用RefreshData函数实现向列表控件中添加数据,RefreshData函数代码如下。

例程3.21代码位置:光盘\第3章\ InstrumentManage\ DlgViewLog.cpp

    01  void CDlgViewLog::RefreshData()
    02  {
    03       m_list.DeleteAllItems();                        //清空列表控件
    04       m_list.SetRedraw(FALSE);                      //不运行重绘
    05       _bstr_t strSQL("SELECT*FROM user_operation");    //查询user_operation表
    06       _RecordsetPtr MySet;                          //记录集对象
    07       int i=0;
    08       try
    09       {
    10           MySet.CreateInstance(__uuidof(Recordset));
    11           MySet=m_DBCnt->Execute(strSQL,NULL,adCmdText);//执行查询
    12           _variant_t Holder;
    13           while(!MySet->adoEOF)                   //遍历查询结果记录集
    14          {
    15              Holder=MySet->GetCollect("do_user");    //操作员
    16               if(Holder.vt!=VT_NULL)
    17                   m_list.InsertItem(i,(char*)(_bstr_t)Holder);//加入列表控件
    18               Holder=MySet->GetCollect("do_date");    //操作时间
    19               if(Holder.vt!=VT_NULL)
    20                   m_list.SetItemText(i,1,(char*)(_bstr_t)Holder);//加入列表控件
    21              Holder=MySet->GetCollect("do_what");    //操作内容
    22               if(Holder.vt!=VT_NULL)
    23                   m_list.SetItemText(i,2,(char*)(_bstr_t)Holder);//加入列表控件
    24               MySet->MoveNext();             //下一记录
    25           }
    26           MySet->Close();                         //关闭记录集
    27       }
    28       catch(_com_error&e)                          //捕捉异常
    29      {
    30         AfxMessageBox(e.ErrorMessage());
    31           m_list.SetRedraw(TRUE);                  //重绘列表控件
    32           return;                                //返回
    33      }
    34       m_list.SetRedraw(TRUE);                      //重绘列表控件
    35  }

第11 行代码执行SELECT查询语句得到user_operation表中的所有记录集,第13~27行代码实现遍历记录集,分别提取记录相应字段的值添加到列表控件中。

在对话框中单击“清空日志记录”按钮,即实现了删除所有日志记录数据操作。“清空日志记录”按钮在对话框类CDlgViewLog中的响应函数为OnBtnVlrmall,其实现代码如下。

例程3.22代码位置:光盘\第3章\ InstrumentManage\ DlgViewLog.cpp

    01  void CDlgViewLog::OnBtnVlrmall()
    02  {
    03       _bstr_t strSQL("TRUNCATE TABLE user_operation");  //构造SQL语句
    04       try
    05       {
    06           m_DBCnt->Execute(strSQL,NULL,adCmdText);  //执行SQL
    07       }
    08       catch(_com_error&e)                          //捕捉异常
    09      {
    10         AfxMessageBox(e.ErrorMessage());
    11           EndDialog(0);                           //退出对话框
    12      }
    13       RefreshData();                               //更新显示列表控件
    14  }

在函数的第6行代码通过执行TRUNCATE TABLE语句删除user_operation表中的所有记录。

3.10 开发技巧和难点分析

3.10.1 _RecordsetPtr记录集指针操作

使用ADO技术进行数据库开发时,在记录集指针(_RecordsetPtr)创建完毕后,可以通过调用该指针的Open函数实现打开记录集。系统开发时,多次用到了该函数,只是调用方式有所不同,这里对其做以下简单介绍。

Open该函数原型如下:

    HRESULT Open( const _variant_t & Source, const _variant_t & ActiveConnection,
            enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options);

各参数的含义如下。

● Source:用于指明打开记录集的数据源,为_variant_t类型的引用。可以为有效的Command对象、SQL语句、表名、存储过程调用、URL或包含持久存储Recordset的文件名或Stream对象等。

● ActiveConnection:为_variant_t类型的引用,是一个连接对象的变量名或者一个包含连接信息的字符串。

● CursorType:用于设置在打开Recordset时提供者应使用的游标类型,它可取枚举CursorTypeEnum中任一值,默认值为adOpenForwardOnly,可能取值及其含义如表3-12所示。

表3-12 打开记录集的游标类型及其含义

● LockType:用于设置在打开记录集时提供者应使用的锁定(并发)类型,它可取枚举LockTypeEnum中任一值,默认值为adLockReadOnly,可能取值及其含义如表3-13所示。

表3-13 打开记录集的锁定类型

● Options:用于设置获取Source的方式,即指定Source中的值的类型,其取值及含义如表3-14所示。

表3-14 Options取值及其含义

函数的返回值为执行命令后得到的记录集。

3.10.2 HRESULT数据类型

使用ADO技术进行数据库开发时,记录集(_RecordsetPtr)对象的操作函数的返回值均是HRESULT数据类型。HRESULT是一种简单的数据类型,通常被属性和ATL用做返回值,其返回的值及说明如表3-15所示。

表3-15 Options取值及其含义

另外,需要注意的是,不能简单地把返回值与S_OK和S_FALSE进行比较,而要用SUCCEEDED和FAILED宏进行判断。

3.10.3 使用CADORecordBinding操作数据库

系统开发中,使用ADO的Connection对象、Recordset对象,以及CADORecordBinding派生类对象实现对数据库的操作。下面给出在Visual C++中,实现的基本步骤。

(1)使用import指令引入ADO组件,例如:

    #import "C:ADOmsado15.dll" no_namespace rename("EOF", "EndOfFile")

(2)定义CADORecordBinding的派生类,用于程序与数据库表字段的交互,具体可参见例程3.1。

(3)调用CoInitialize初始化COM,例如:

            ::CoInitialize(NULL);

(4)声明ADO的Connection对象指针和Recordset对象指针并初始化,例如:

    _ConnectionPtr pConnection1 = NULL;
    _RecordsetPtr rstADO1 = NULL;

(5)定义CADORecordBinding派生类的实例及其Bind接口指针,例如:

    CDevCodeRs m_devcode;
    IADORecordBinding *rstADOBind1 = NULL;

(6)产生Connection对象实例和Record set对象实例,例如:

    pConnection1.CreateInstance(_uuidof(Connection));
    rstADO1.CreateInstance(__uuidof(Recordset)) ;

(7)连接到数据库并打开RecordSet对象,例如:

    PConnection1->Open("driver={SQL server};server=servera;uid=sa;
                  pwd=;database=pubs","","",NULL);
    rstADO1->Open("data", _variant_t((IDispatch *)pConnection1,true), adOpenKeyset,
              adLockBatchOptimistic, adCmdTable);

(8)将CADORecordBinding派生类的实例联编到RecordSet对象的Bind接口,例如:

    RstADOBind1->BindToRecordset(&m_devcode);

(9)对RecordSet对象实例进行操作,例如:

    rstADO1->Move Next(); //移动游标到下一条记录
    rstADO1->Update(_variant_t("quality"),_variant_t("3"))); //修改记录的quality字段的值为3
    rstADO1->Update Batch(adAffectAll)); //将在Record set对象上的所有更新一次送入数据库

(10)操作完毕,关闭Record set对象并释放Bind接口,例如:

    RstADO1->Close();
    RstADOBind2->Release();

(11)关闭连接,例如:

    pConnection1->Close();

(12)调用CoUnitialize释放COM资源,例如:

            ::CoUninitialize();