8.4数据库相关的ActiveX控件
在前面的数据库处理中,一次只能显示一行记录,且修改或添加操作不能可视化地进行。为了弥补MFC的这种不足,在VisualC++6.0中允许用户使用一些
ActiveX控件更好地处理数据,它们是:MSFlexGrid,RemoteData,DBGrid等。
8.4.1使用MSFlexGrid控件
Microsoft FlexGrid(MSFlexGrid)控件可以显示网格数据,也可以对其进行操作。它提供了高度灵活的网格排序、合并和格式设置功能,网格中可以包含字符串和图片。利用MSFlexGrid可以将某个表的所有记录显示。下例说明其使用过程。
例:使用RemoteData和DBGrid控件
1、将控件MSFlexGrid的类添加到项目中
要对添加的控件进行编程,首先要将控件的类添加到项目中,步骤如下:
A、选择”Project”->”Add To Project”->Commponents and Controls…”,弹出
“Componentsand Controls Gallery”对话框,如下图9所示:
B、双击“Registered ActiveX Controls”项,将列出在Windows98系统中安装的
ActiveX控件。
C、在列表中找到Microsoft FlexGrid Control(见下图10),在该控件双击鼠标
Visual C++会弹出一个对话框,向用户询问是否将控件插入项目中。
E、单击[确定]按钮,弹出“ConfirmClasses”对话框。
F、单击[OK]按钮接受所有的类,看到该控件已添加到对话框编辑器的控件工具
栏中。
G、单击[Close]按钮,关闭“Componentsand Controls Gallery”对话框
2、向对话框添加MSFlexGrid控件
向对话框添加MSFlexGrid控件与其它常用控件添加的方法一样。MSFlexGrid控件也有自己的属性,如:通用、样式、字体、颜色、图片等,下图11所示。这些属性不仅能设置控件的字体、颜色,而且能设置网格的行数和列数以及其它的功能,它的可视化程度一般都比Visual C++的常用控件要高得多。
图9:“Componentsand Controls Gallery”对话框
图10:选中Microsoft FlexGrid Control
图11:MSFlexGrid控件的属性对话框
具体做法:
先将你刚加到控件表中的MSFlexGrid控件,拖到对话框(ID_MY_FORM)中,ID为:IDC_MSFLEXGRID1,并在View类加一个MSFlexGrid类的成员变量m_MSFGrid,然后,在视图类(View)中的OnInitialUpdate函数体中添加代码:
m_MSFGrid.SetCols(m_pSet->m_nFields+1);//设置望格的最大列数
m_MSFGrid.SetRows(m_pSet->GetRecordCount()+1);
m_MSFGrid.SetColWidth(-1,1440);
m_MSFGrid.SetRow(0);
m_MSFGrid.SetCol(1);
m_MSFGrid.SetText("学号");
m_MSFGrid.SetRow(0); m_MSFGrid.SetCol(2);
m_MSFGrid.SetText("姓名");
m_MSFGrid.SetRow(0); m_MSFGrid.SetCol(3);
m_MSFGrid.SetText("性别");
m_MSFGrid.SetRow(0); m_MSFGrid.SetCol(4);
m_MSFGrid.SetText("专业代号");
int iRow=1;
while(!m_pSet->IsEOF())
{
CString str;
str.Format("记录%d",iRow);
m_MSFGrid.SetRow(iRow);m_MSFGrid.SetCol(0);
m_MSFGrid.SetText(str);
m_MSFGrid.SetRow(iRow);m_MSFGrid.SetCol(1);
m_MSFGrid.SetText(m_pSet->m_stuid);
m_MSFGrid.SetRow(iRow);m_MSFGrid.SetCol(2);
m_MSFGrid.SetText(m_pSet->m_name);
m_MSFGrid.SetRow(iRow);m_MSFGrid.SetCol(3);
m_MSFGrid.SetText(m_pSet->m_sex);
m_MSFGrid.SetRow(iRow);m_MSFGrid.SetCol(4);
m_MSFGrid.SetText(m_pSet->m_profcode);
iRow++;
m_pSet->MoveNext();
}
m_MSFGrid.SetRow(1);
m_MSFGrid.SetCol(1);
m_pSet->MoveFirst();
}
如上代码的目的,是将m_pSet所关联的表的记录内容显示在MSFlexGrid控件上,结果下图12所示:
图12:MSFlexGrid控件的结果
8.4.2RemoteData和DBGrid控件
MSFlexGrid控件提供界面友好的网格,使表的记录内容能全部地显示出来,但却没有表处理的常用功能,如添加记录、修改记录和删除记录等。而DBGrid控件就有这方面的功能,它不仅能显示全部表的记录内容,而且能很好地支持常见的记录操作。
需注意的是DBGrid控件还必须用RemoteData控件来提供数据源,它最大的
好处是不需要任何程序代码就能实现表的处理。
(1)RemoteData控件
通过被绑定的控件存取存储在远程ODBC数据源中的数据。RemoteData控件允许在某一记录集的行与行之间移动,且允许显示和操作来自于被绑定的控件各行里的数据。RemoteData控件在远程数据对象(RDO)和数据识别的被绑定的控件之间提供了接口。通过RemoteData控件,能够:
1)建立起与基于其本身属性的数据源的连接
2)创建RDO的结果集
3)把当前的数据传给相应被绑定的控件
4)允许对当前行指针进行定位
5)将对被绑定的控件所做的任何更改反传给数据源
l 向IDD_MY_FORM对话框添加RemoteData控件
由于RemoteData控件一般不需要程序再控制,因此向一个对话框中添加
RemoteData控件时,只需要在IDD_MY_FORM对话框中右击鼠标à弹出快捷
菜单à选择“Insert Active Control”命令à从“Insert Active Control”对话框的
控件列表中选择Microsoft RemoteData Control,version控件à单击OKà见
RemoteData控件出现在了对话框上(这种方法均适用于不需要程序控制的所
有ActiveX控件,例如DBGridContrl控件).
将RemoteData控件拖到右稍下适当位置,拉长并拖窄一些:
1)右键单击RemoteData控件àpropertiesà颜色à双击“自定义”à选“红色”
à确定。
2) 右键单击RemoteData控件àpropertiesà点“Control(控制)”à从“DataSource”的下拉列表中找到你起的数据源名(如:My database for VC)置好。并在“SQL”编辑框中键入SQL操作语句,例如:“SELECT *FROM xs”(是检索学生表xs的所有记录)。如下图13所示:
图13:RemoteData控件的属性对话框
3) 右键单击RemoteData控件àpropertiesà点“ALL”à将出现的ALL页面的
“CursorDriver”栏下拉à置为“1-ODBC cursor”。由于RemoteData控件在这
里是用于ODBC的联结,因此该控件必须使用ODBC的游标驱动程序
(CursorDriver),使其置为1-ODBCcursor。如下图
图14:RemoteData控件属性“ALL”页面的”CursorDriver设置
设置DBGrid控件
一旦上面添加在对话框的RemoteData控件的属性设定后,那麽在同一个对
话框的DBGrid控件就可以与RemoteData控件进行数据绑定。
l 在IDD_MY_FORM对话框上加一个DBGrid控件
在IDD_MY_FORM对话框中右键击鼠标à弹出快捷菜单à选择“Insert Active
Control”命令à从“Insert Active Control”对话框的控件列表中选择DBGrid
Contrl 控件à单击OKà见DBGrid 控件出现在了对话框上。见下图17。
图15:向IDD_MY_FORM对话框添加DBGridContrl控件
将DBGrid Contrl控件拖到右上面适当位置,拉大成方形。
l 右键单击DBGrid 控件à propertiesà颜色à双击“自定义”à选“黄色”
à确定。
l 右键单击DBGrid 控件à propertiesà点“ALL”à将出现的ALL页面à选中
DataRource栏à右边下拉,设置为IDC_REMOTEDATACTL1(RemoteData控件的ID标识符,这时该2个控件就绑定了)。见下图18:
图16:DBGrid控件属性“ALL”页面的”DataSource”设置
4) 编译运行,结果见下图17所示。
图17:最终结果界面
8.5字段操作
在前面的示例中,虽然可以通过CRecordSet对象中的字段关联变量可以直接访问当前记录的相关字段值,但有时在处理多个字段时就不太方便了。CRecordSet类中的成员变量m_nFields(用于保存数据表的字段个数)和成员函数GetODBCFieldInfo及GetFieldValue可以简化多字段的访问操作。
GetODBCFieldInfo函数用于数据表中的字段信息,其函数原型如下:
voidGetODBCFieldInfo(short nIndex,CODBCFieldInfo &fieldinfo);
参数:nIndex用于指定字段索引号,0表示第1个字段,1表示第2个字段,…
Fieldinfo是CODBCFieldInfo结构参数,用于表示字段信息
CODBCFieldInfo结构体原型如下:
StructCODBCFieldInfo
{ CString m_strName; //字段名
SWORD m_nSQLType; //字段的SQL数组类型(SWORD表示short int短整形)
UDWORD m_nPrecision;//字段的文本大小或数据大小(UDWORD表示
//unsigned long int无符号长整型)
SWORD m_nScale; //字段的小数点位数
SWORD m_nNullability; //字段接受空值(NULL)能力
};
GetFieldValue函数用于获取数据表当前记录中指定字段的值,其常用的函数原型如下:
voidGetFieldValue(short nIndex,CString &strValue);
参数:nIndex用于指定字段索引号
strValue用于返回字段的内容
除了上述字段操作外,CRecordSet类的成员函数GetRecordCount和GetStatus还可分别用于获得表中的记录总数和当前记录的索引,其原型如下:
longGetRecordCount()const;
voidGetStatus(CRecordsetStatus &rStatus)count;
参数:rStatus是指向下列的CRecordsetStatus结构的对象:
StructCRecordsetStatus
{ long m_lCurrentRecord;//当前记录的索引,0表示第1个记录,1表示第2个记录,
//依次类推。-1表示在第1个记录之前,-2表示不确定
BOOL m_bRecordCountFinal;//记录总数是否是最终结果
};
GetRecordCount函数所返回的记录总数在表打开时或调用Requery函数后是不确定的,必须经过下列代码才能获得最终有效的记录总数:
while(!m_pSet->IsEOF())
{ m_pSet->MoveNext();
m_pSet->GetRecordCount();
}
例:字段的编程操作
用列表视图来显示一个课程信息表(见349表8.5)的全部记录内容,并在状态栏中显示记录总数。
1、为数据库Studentf.mdb添加一个数据表course,
下面我们开始用Access2003设计数据库:
(1)开机正常屏幕状态à开始à程序àMicrosoft OfficeàMicrosoft Office Access 2003à新建文件à空数据库à数据库名处写:studentfà点右边的创建à新建à设计视图à确定à出现表,你按书349页表8.5的下部分敲入数据即可。
(2)关闭此表à提问:你是否保存对表1的设计更改?à是à表名写courseà是à便在数据库中建立了表course
(3)你双击course表出现设计视图表,之后你按书349页表8.5上面部分敲进数据即可
2、为文档应用程序添加ODBC的支持
(1)建一个单文档应用程序名为:字段操作,在向导的第6步将View的基类由默认的CView选择为CListView类
(2)将项目工作区窗口切换到FileView页面,展开Header Files所有项,双击stdafx.h打开该文件
(3)在stdafx.h中添加ODBC数据库支持的头文件包含#include <afxdb.h>如下:
#endif//_AFX_NO_AFXCMN_SUPPORT
#include<afxdb.h>
3、创建数据表score的CRecordSet派生类
(1)View->ClassWizard->Add Class…下拉->New(加新类)
(2)在弹出的“Add Class”对话框中定义新的CRecordSet的派生类CCourseSet,如书350页图8.23所示。
(3)(4)
(5) cs.style &=~LVS_TYPEMASK;
cs.style|=LVS_REPORT;
(6) CListCtrl&m_ListCtrl=GetListCtrl();//获取内嵌在列表视图中的列表控件
CCourseSet cSet;
cSet.Open();//打开记录
CODBCFieldInfo field;
//创建列表头
for(UINT i=0;i<cSet.m_nFields;i++)
{
cSet.GetODBCFieldInfo(i,field);
m_ListCtrl.InsertColumn(i,field.m_strName,LVCFMT_LEFT,100);
}
//添加列表项
int nItem=0;
CString str;
while(!cSet.IsEOF())
{
for(UINTi=0;i<cSet.m_nFields;i++)
{
cSet.GetFieldValue(i,str);
if(i==0)
m_ListCtrl.InsertItem(nItem,str);
else
m_ListCtrl.SetItemText(nItem,i,str);
}
nItem++;
cSet.MoveNext();
}
cSet.Close();//关闭记录集
(7)在CMyView.cpp文件的前面加:#include “CourseSet.h”
说明:当为数据源中的某个数据表映射一个CRecordSet类时,该类对象一定先要调用CRecordSet::Open成员函数,才能访问该数据表的记录集,访问后还须调用CRecordSet::Close成员函数关闭记录集。
(8)编译运行,结果见351页图8.24
4、在状态栏中显示当前记录号和记录总数
(1)
(2)在CMyView.cpp文件前面的#endif后加:
voidDispRecNum(CCourseSet *pSet)
{
CStringstr;
CMainFrame *pFrame= (CMainFrame *)AfxGetApp()->m_pMainWnd;//获得主框
//架窗口的指针
CStatusBar*pStatus = &pFrame->m_wndStatusBar;//获得主框架窗口中的状态栏指针
if(pStatus)
{
CRecordsetStatus rStatus;
pSet->GetStatus(rStatus);//获得当前记录信息
str.Format("当前记录:%d 总记录:%d",1+rStatus.m_lCurrentRecord,
pSet->GetRecordCount());
pStatus->SetPaneText(1,str);
}
}
该函数先获得状态栏对象的指针,然后调用SetPaneText函数更新第2个窗格的文本。(3)在前面CMyView.cpp的OnInitialUpdate函数中刚才已经加好的代码最后一
条语句前面加: ::DispRecNum(&cSet);
(4)在CMyView.cpp文件的开始处添加:”MainFrm.h”
(5)将MainFrm.h文件中的保护型变量m_wndStatusBar剪切到public:里
(6)编译运行,结果见353页图8.25
8.6多表处理(此例是以树控件形式显示学生成绩)
数据库中表与表之间往往存在着一定的关系,例如:要显示一个学生的课程成绩信息,包括学号、姓名、课程号、课程所属专业、课程名称、课程类别、开课学期、课时数、学分、成绩,则要涉及前面的学生课程成绩表(score)、课程表以及学生基本信息表。以下示例是在一个对话框中用2个控件来进行学生课程成绩信息的相关操作,如353页图8.26所示,其左边是树视图,用于显示学生成绩、专业和班级号3个层次信息,单击班级号,所有该班级的学生课程成绩信息将在右边的列表视图中显示出来。
例:多表处理
1、为数据库Student.mdb添加一个数据表: student和score
双击Microsoft Office Access 2003在右边“打开”(已有的数据库名)的下面双击studentf(我们以上课里建好的数据库),便出现一个对话框,你点“打开”又出现一个“studentf:数据库”对话框->你点新建->设计视图->确定->开始输入数据,你将书354页表8.6的下表数据敲入到数据库中,表的名字为:student
这时在studentf数据库中右出现一个student表(原来有个course表),你双击它便开始敲入354页表8.6的上表数据。
以同样的方法再建一个score表,该表的内容为336页表8.1(studentf里共有
三个表,即:course、student、score)
2、创建并设计对话框应用程序
(1)创建一个基于对话框的应用程序名为:处理多个表
(2)(3)(4)(5)(6)
3、添加对MFC ODBC的支持及记录集
(1)#include <afxdb.h>
(2)
4、完善左边树控件的代码
(1)为CMyDlg类添加一个成员函数:ClassView->右键对准CMyDlg单击->
AddMember Function->Function Type处写:HTREEITEM->Function Declaration处写:FindTreeItem(HTREEITEM hParent, CString str)用于查找指定结点下是否有指定结点文本的子结点,函数代码如下:
并加如下代码:
HTREEITEM CMyDlg::FindTreeItem(HTREEITEMhParent, CString str)
{
HTREEITEM hNext;
CString strItem;
hNext=m_treeCtrl.GetChildItem(hParent);
while(hNext!=NULL)
{
strItem=m_treeCtrl.GetItemText(hNext);
if(strItem==str)
return hNext;
else
hNext=m_treeCtrl.GetNextItem(hNext,TVGN_NEXT);
}
return NULL;
}
(2)用右键对准CMyDlg类,为CMyDlg类加一个CImageList类的成员变量m_ImageList
(3)m_ImageList.Create(16,16,ILC_COLOR8|ILC_MASK,2,1);
m_treeCtrl.SetImageList(&m_ImageList,TVSIL_NORMAL);
SHFILEINFO fi; //定义一个文件信息结构变量
SHGetFileInfo("C:\\Windows",0,&fi,sizeof(SHFILEINFO),
SHGFI_ICON|SHGFI_SMALLICON);//获取文件夹图标
m_ImageList.Add(fi.hIcon);
SHGetFileInfo("C:\\Windows",0,&fi,sizeof(SHFILEINFO),
SHGFI_ICON|SHGFI_SMALLICON|SHGFI_OPENICON);//获取打开文件夹图标
m_ImageList.Add(fi.hIcon);
HTREEITEM hRoot,hSpec,hClass;
hRoot=m_treeCtrl.InsertItem("学生成绩",0,1);
CStudentSet sSet;
sSet.m_strSort="special";//按专业排序
sSet.Open();
while(!sSet.IsEOF())
{
hSpec=FindTreeItem(hRoot,sSet.m_special);
//查找是否有重复的专业结点
if(hSpec==NULL)//若没有重复的专业结点
hSpec=m_treeCtrl.InsertItem(sSet.m_special,0,1,hRoot);
hClass=FindTreeItem(hSpec,sSet.m_studentno.Left(6));
//查找是否有重复的班级结点
if(hClass==NULL)//若没有重复的班级结点
hClass=m_treeCtrl.InsertItem(sSet.m_studentno.Left(6),0,1,hSpec);
sSet.MoveNext();
}
sSet.Close();
(4)#include"StudentSet.h"
#include "ScoreSet.h"
#include "CourseSet.h"
(5)编译运行,第1次结果与356页图8.27一样
5、完善右边列表控件的代码
(1) //设置列表头
CString strHeader[]={"学号","姓名","课程号","课程所属专业","课程名称",
"课程类别","开课学期","课时数","学分","成绩"};
intnLong[]={80,80,80,180,180,80,80,80,80,80};
for(intnCol=0;nCol<sizeof(strHeader)/sizeof(CString);nCol++)
m_listCtrl.InsertColumn(nCol,strHeader[nCol],LVCFMT_LEFT,nLong[nCol]);
(2)为CMyDlg类添加一个成员函数:ClassView->右键对准CMyDlg单击->
AddMember Function->Function Type处写:void->Function Declaration处写:DispScoreAndCourseInfo(CString strFilter)用于根据指定的条件在列表控件中用报表形式显示学生成绩的所有信息,代码如下:
voidCMyDlg::DispScoreAndCourseInfo(CString strFilter)
{
m_listCtrl.DeleteAllItems();//删除所有的列表项
CScoreSet sSet;
sSet.m_strFilter=strFilter;//设置过滤条件
sSet.Open();//打开score表
int nItem=0;
CString str;
while(!sSet.IsEOF())
{
m_listCtrl.InsertItem(nItem,sSet.m_studentno);//插入学号
//根据score表中的studentno(学号)获取student表中的“姓名”
CStudentSet uSet;
uSet.m_strFilter.Format("studentno='%s'",sSet.m_studentno);
uSet.Open();
if(!uSet.IsEOF())
m_listCtrl.SetItemText(nItem,1,uSet.m_studentname);
uSet.Close();
m_listCtrl.SetItemText(nItem,2,sSet.m_course);
//根据score表中的course(课程号)获取course表中的课程信息
CCourseSet cSet;
cSet.m_strFilter.Format("courseno='%s'",sSet.m_course);
cSet.Open();
UINT i=10;
if(!cSet.IsEOF())
{
for(i=0;i<cSet.m_nFields;i++)
{
cSet.GetFieldValue(i,str);//获取指定字段值
m_listCtrl.SetItemText(nItem,i+2,str);
}
}
cSet.Close();
str.Format("%0.1f",sSet.m_score);
m_listCtrl.SetItemText(nItem,i+2,str);
sSet.MoveNext();
nItem++;
}
if(sSet.IsOpen())
sSet.Close();
}
(3)编译运行,第2次结果与358页图8.28一样
6、完善两控件的关联代码
上面作完,学生成绩还没有显示出来,下面就实现单击左边的班级号,在右边的视图中显示该班级的所有学生成绩信息。
(1)用MFC ClassWizard为CMyDlg类添加树控件(选中IDC_TREE1,右边找到TVN_SELCHANGED消息映射)TVN_SELCHANGED消息处理,并加下列代码:
voidCMyDlg::OnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView =(NM_TREEVIEW*)pNMHDR;
// TODO: Add your control notificationhandler code here
HTREEITEMhSelItem=pNMTreeView->itemNew.hItem;//获取当前选择的结点
//如果当前的结点没有子结点,那说明该结点是班级号结点
if(m_treeCtrl.GetChildItem(hSelItem)==NULL)
{
CString strSelItem,str;
strSelItem=m_treeCtrl.GetItemText(hSelItem);
str.Format("studentnoLIKE'%s%%'",strSelItem.Left(6));
DispScoreAndCourseInfo(str);
}
*pResult = 0;
}
代码中,调用DispScoreAndCourseInfo函数的参数是用于设置数据表(记录集)打开的过滤条件。str是类似内容:”studentno LIKE 210101%”,它使所有学号前面是210101的记录被打开。%是SQL使用的通配符,由于%也是Visual C++格式前导符,因此在代码中需要2个%号。
(2)编译运行,达到预想效果
第8章结束