这个学期回来,才知道,时间是有的,所以就抓紧时间学VC.学VC,首先学的应该是MFC吧~于是就开始了15天的MFC之旅.
还记得第一天开始学习,是星期五,刚刚开完会回来,躺在床上,很不爽,就立即起来开始看VC.那天下午,真够不爽,但是也给了我足够的信心去学VC.那时候还觉得应该学完<WINDOWS程序设计>再看MFC的.但是时间比较紧迫,还是把MFC学了再说吧.
刚刚打开那本<精通MFC>,似乎很熟悉,但是却也很陌生.因为有了CB的基础,所以MFC中的控件\类 \消息处理等等都觉得很熟悉,但是看到就这么几个控件,比起CB的十几个标签的控件,显得似乎真的难很多.加上同学说,VC什么都要自己写.所以看着书,也比较纳闷.那天下午,把说概念论述的几张给看完了,大致知道了什么是MFC.也明白了MFC可以做些什么,不过到现在.仍然觉得有几个消息重载起来比较难理解,还有就是钩子.
但对着通用控件的时候,又觉得很熟悉.因为很多IDE都有控件这个东西,用起来也大同小异.
可能由于专项测试是用CB来做COMBOBOX的递进搜索,所以也想用这个测试来把自己引入VC.于是就开始使用通用控件中的COMBOBOX了.一晚的时间,把COMBOBOX的基本递进搜索给搞好了,这时候觉得自信心很够,想把剩下的东西,星期六再来摸索,怎么知道星期六一开始用VC,就碰丁了,因为不了解VC的一些消息的重载,怎么搞也处理不了KEYDOWN和KEYUP两个事件,使得循环用不了.还有其他的一些问题.这不得使我要继续学习MFC,再做测试.
于是,开始通篇的看书.看了一天,星期天继续测试,由于上次做的是COMBOBOX了,这次又做COMBOBOX觉得不爽,于是就想做一个像点样子的班级成绩管理系统.但是我的计划是在国庆前完工,可能吗?那时候也没有想那么多.只想着尽管试试吧.就开始实行了.
做一个班级成绩管理系统,顾名思义,就知道应该是一个数据库系统,所以首先要设计数据表.由于这次的经验不足,刚刚开始定位的不确定,致使我在显示数据这个地方,浪费了很多的时间.由于刚刚开始,想的是科目已经设定的管理系统,所以想面向的是全校,但是想了一下,就知道这样是很难实现的,一来要顾及校区,二来要顾及班级,专业,人数,做起来比较困难,而且很不现实,不可能一个学校都是考语\数\英吧?于是只能改数据表,变回班,如果是一个班,就这么三科,其他的科目都不允许添加,这样也不现实,所以只能再设计数据表.这里有了个分岔,我朋友说觉得是动态建立数据表,即考一次试,就建一张表,但是我觉得这样很不现实,扩展性也不强,万一我要把一个班的做成一个系的,就很难修改,最后的定论是,利用四张表,科目表,学生信息表,考试表,考生信息表,利用表关联来解决表的问题.解决了表的问题.就可以用VC来写管理系统.这种管理系统也可以分成三种形式,一个是基于多文档的,一个是基于单文挡的,还有一个是基于对话框的,基于对话框的比较容易建立,也比较直观,但是不够灵活,如果基于多文档,又觉得过于复杂,于是选了基于单文档,然后多视图.
既然要多视图,就必须分割窗口,由于这个系统不算复杂,所以也不需要用到动态分割.利用了CSplitterWnd这个类,新建一个对象,然后利用CreateStatic()函数来分割窗口,再利用CreateView()函数来填充视图.这里要注意一个很重要的地方,这里的视图都是填充对话框的,所以ListControl和Tree都是在对话框里面的控件,而不是用ListView或者TreeView,所以如果在初始化的时候没有把ListControl和Tree随着分割后的窗口拉伸,就会很难看,所以在初始化视图的时候,应该注意加入以下代码: (用Tree控件来做例子)
void CLeftTreeView::OnSize(UINT nType, int cx, int cy)
{
CFormView::OnSize(nType, cx, cy);
CFormView::ShowScrollBar(SB_BOTH,FALSE);
//使树控件填满整个视图
if (GetSafeHwnd())
{
CRect rect;
GetClientRect(&rect);
if (m_treeCtrl.GetSafeHwnd())
m_treeCtrl.MoveWindow(&rect);
}
}
解决了视图的问题,就应该在里面添加相应的内容,以下难点有四个方面,一个是要找到视图的指针,第二个是控件怎么传值,第三个是ADO怎么连接数据库,第四个是ListControl怎么显示数据.
先来解决第一个问题:由于我没有用到辅助框架,所以全部都应该由主框架来引出.例如我要info里面显示ListControl的值,那么我首先应该在ListControl里面加入info的头文件以及主框架的头文件->#include "CInfoView.h"-#include "MainFrm.h"这样才能把Info的类以及主框架的类包进ListControl所在的.cpp文件中.再入下面的代码:
CMainFrame* pMainFrm=(CMainFrame*)AfxGetMainWnd();
CInfoView* pInfoView;
pInfoView=(CInfoView*)pMainFrm->m_wndSplitter3.GetPane(1,0);
pInfoView->
到第二个问题了,控间的传值,利用的是一个叫UpdateData()的函数,该函数要的参数是BOOL型,即TRUR或FALSE;
当你使用了ClassWizard建立了控件和变量之间的联系后:当你修改了变量的值,而希望对话框控件更新显示,就应该在修改变量后调用UpdateData(FALSE);如果你希望知道用户在对话框中到底输入了什么,就应该在访问变量前调用UpdateData(TRUE)。
第三个问题是重点,连接数据库的方式也有很多种,有ODBC API/MFC ODBC\ DAO\ OLE DB/ADO.而我这次选择的是ADO来连接,因为比较简单而且应用得比较广泛,它是应用层的编程借口,以OLE DB为基础,并对OLE DB进行封装.一般通过OLE DB提供的COM借口来访问数据.
由于在数据库开发的时候,操作数据库的代码用得是比较多的,因此应该把这些代码封装在一个类里面,叫做ADOConn类,里面应该包括初始化,打开数据集之类的功能.这样会减少代码的重复,达到代码结构简洁的效果.另外就是在把数据从数据库导入VC的时候,是_variant_t这一类型的数据,应该把它转换为CString类型,这样在VC里面操作才方便.但是VC里面并没有提供比较好的转换函数,因此应该自己重新写一个转换函数.以下是在网上载的万能转换:
CString ADOConn::VariantToCString(const _variant_t &var)
{
CString strValue;
switch (var.vt)
{
case VT_BSTR: //字符串
case VT_LPSTR:
case VT_LPWSTR:
strValue = (LPCTSTR)(_bstr_t)var;
break;
case VT_I1: //无符号字符
case VT_UI1:
strValue.Format("%d", var.bVal);
break;
case VT_I2: //短整型
strValue.Format("%d", var.iVal);
break;
case VT_UI2: //无符号短整型
strValue.Format("%d", var.uiVal);
break;
case VT_INT: //整型
strValue.Format("%d", var.intVal);
break;
case VT_I4: //整型
case VT_I8: //长整型
strValue.Format("%d", var.lVal);
break;
case VT_UINT: //无符号整型
strValue.Format("%d", var.uintVal);
break;
case VT_UI4: //无符号整型
case VT_UI8: //无符号长整型
strValue.Format("%d", var.ulVal);
break;
case VT_VOID:
strValue.Format("%8x", var.byref);
break;
case VT_R4: //浮点型
strValue.Format("%.2f", var.fltVal);
break;
case VT_R8:
strValue.Format("%.2f", var.dblVal);
break;
case VT_DECIMAL: //小数
strValue.Format("%.2f", (double)var);
break;
case VT_CY:
{
COleCurrency cy = var.cyVal;
strValue = cy.Format();
}
break;
case VT_BLOB:
case VT_BLOB_OBJECT:
case 0x2011:
strValue = "[BLOB]";
break;
case VT_BOOL: //布尔型
strValue = var.boolVal ? "TRUE" : "FALSE";
break;
case VT_DATE: //日期型
{
DATE dt = var.date;
COleDateTime da = COleDateTime(dt);
strValue = da.Format("%Y-%m-%d %H:%M:%S");
}
break;
case VT_NULL://NULL值
case VT_EMPTY://空
strValue = "";
break;
case VT_UNKNOWN: //未知类型
default:
strValue = "UN_KNOW";
break;
}
return strValue;
}
这样就使得数据转换变得很方便了.还有一个关于数据转换需要注意的是:float\int\CString之间的转换.
下面给个例子:
credit=atof(credit1.GetBuffer(credit1.GetLength()));
credit是一个double类型的数,credit1是一个CString类型的.
第四个问题,刚刚开始觉得很难写,因为要写循环来解决. 首先在数据表里面查询考生姓名以及该考试的科目,插入列头,再在数据表里面,根据考生姓名及相应的科目把数据填在ListControl上.这里不提供循环的写法,因为各人设计的不同,所以填充数据也不同.
问题出现在当你要重新显示List的时候,这时候你应该先删除所List上的所有内容,包括列头,因此很多人都会用通常的方法来删除.
int columns1=m_listAverge.GetHeaderCtrl()->GetItemCount();
for(k=0;k<columns;k++)
m_listAverge.DeleteColumn(k);
m_listAverge.DeleteAllItems();
m_listAverge是List的一个实例.如果真的是这么做,就会出现问题.你会看到,List里面的内容并没有删除完,所以当你再加入数据的时候,List里面的排版就乱了.应该用以下的方法:
int columns1=m_listAverge.GetHeaderCtrl()->GetItemCount();
for(k=columns1;k>=0;k--)
m_listAverge.DeleteColumn(k);
m_listAverge.DeleteAllItems();
这样倒过来删除,才能真正把List里面的数据删除完.该方法也适用解决ListBox和ComboBox的删除所有行的问题.原因在于当你删除上面的一行,下面那行的行号就会往上跳,因此,你只删除了一半的行,还有一半的行没有删除.而倒过来删除的话,行不是会往后跳的,所以不会出现这种情况.这与CB不同.
四大问题解决了,下面来说说扩展功能,这次我用到的还是操作EXCEL.但是这次并没有用OLE来操作,OLE操作的方法大致和CB的一样,我这次是用ADO的_ConnectionPtr类来完成.但是还有一点问题没有解决.就是不懂怎么删除EXCEL的表.即我输出第一次后,第二次想再输出的时候,就会发生错误.部分代码如下:
_ConnectionPtr mcon;
mcon.CreateInstance("ADODB.Connection");
CString constr;
c+ExameName+".xls;\
Extended Properties = Excel 8.0";
mcon->Open(_bstr_t(LPCTSTR(constr)),"","",adModeUnknown);
这里已经新建了EXCEL表,然后就是要用try{ mcon->Execute(_bstr_t(LPCTSTR(sSql)), NULL, adCmdText);}
catch(){}
这就是我十五天学习MFC的一些经验总结.MFC十五天是不可能学得完,也不可能做到精通,例如多线程,DLL,COM还有网络协议方面的,都没有学习到,这在今后学习的时候一定要学习才行,学到了,再与大家分享.