前言:最近专业选修课数据库结课,最后需要使用Visual FoxPro 设计一个项目,为此自己和队友设计了一个小型的教务管理系统。
根据两种不同的用户身份:学生和老师,设计两种不同的系统,或者说两种不同的菜单,其中老师赋予的是管理员权限,可以对学生相关信息查看和更改。 菜单框图如下:
1)查看成绩 老师可以根据学号和姓名两种模式来查找学生的各科成绩,动态刷新表格 2)查看课程表
查看课程表界面,可以根据已知学号信息进行查找对应同学的课表,同时显示同学的姓名。 (3)增删用户 增加操作,可以对系统中的账号信息进行增加。 删除操作,但是不能删除管理员,即老师的信息,否则会报错提示。 (4)切换账号 即可以切换账号,实现登录界面和用户身份的快速切换 (5)更改密码 登陆者可以重设自己的密码 (6)退出系统 退出系统,弹出提示框确认是否退出
(1)我的课程表 学生可以查看自己的课程表,并提供课程表打印预览和打印功能 (2)我的成绩 登陆者可以查看自己的成绩,并提供多条件查询功能: 显示时可以根据成绩递增或递减两种模式 可以根据课程属性:必修,选修,公选查询 可以根据课程名称:查找单科成绩情况 (3)切换账号 和老师菜单功能相同 (4)更改密码 和老师菜单功能相同 (5)退出系统 和老师菜单功能相同
系统由项目管理器生成,综合利用数据库设计,表设计,表单设计,菜单设计,报表设计,程序设计,项目管理器连遍等,最后生成可执行文件实现系统运行。 文件主要结构如下图所示:(省略!)
注意:为了体现索引设计,在下面图中标注表间关联,实际上各个表是独立的,是利用代码调用建立起关联。。。 包括三类表: 个人信息表:user,字段包括:id password name class; 学生成绩表:score,字段包括:id,课程名称,成绩,课程属性,学分,绩点 学生课程表:course,字段包括:class,课时,周一,周二,…,周天 标间联系: 以个人信息表的id建立索引,和成绩表的id建立关联 以个人信息表的class建立索引,和课程表的class建立关联 如下图所示:
学生查询系统主要涉及学习成绩查询表单scoreform.scx,课程表查询表单courseform.scx
表单中主要涉及标签、命令按钮、表格、选型按钮组、文本框的使用 标签设置较简单,只需要修改相应标签名和字体格式大小;选项按钮组通过在其生成器中修改按钮数目和标题即可,在判断选项时,可通过do case语句进行value的判断。对于表格属性的设置,由于需要动态刷新表格,因此查询相应资料得知,可以将表格的数据源设置为RecordsourceType为”4-SQL说明” (1)成绩显示按钮:click事件
do case case thisform.Optiongroup2.value=1 thisform.Grid1.recordsource="select score_st3.课程名称,成绩,; 课程属性,学分,绩点 from score_st3 order by score_st3.成绩 ASC into cursor temp" case thisform.Optiongroup2.value=2 thisform.Grid1.recordsource="select score_st3.课程名称,成绩,; 课程属性,学分,绩点 from score_st3 order by score_st3.成绩 DESC into cursor temp" endcase thisform.refresh()整体采用do case语句进行选项按钮组的值判断,同时利用select查询语句进行条件判断,由于是按照成绩的值进行排序,所以条件语句选择order by ,采用cursor函数产生临时表单,用来储存查询后的结果。 (2)课程性质查询按钮:click事件
do case case thisform.Optiongroup1.value=1 thisform.Grid1.recordsource="select score_st3.课程名称,成绩,; 课程属性,学分,绩点 from score_st3 where score_st3.课程属性='必修' into cursor temp" case thisform.Optiongroup1.value=2 thisform.Grid1.recordsource="select score_st3.课程名称,成绩,; 课程属性,学分,绩点 from score_st3 where score_st3.课程属性='选修' into cursor temp" case thisform.Optiongroup1.value=3 thisform.Grid1.recordsource="select score_st3.课程名称,成绩,; 课程属性,学分,绩点 from score_st3 where score_st3.课程属性='公选' into cursor temp" endcase thisform.refresh()同理,查询采用select语句,只是将其中条件语句采用where进行规定 (3)课程名称查询语句:click事件
select score_st3 thisform.Grid1.recordsource="select score_st3.课程名称,成绩,; 课程属性,学分,绩点 from score_st3 where score_st3.课程名称=alltrim(thisform.text1.value) into cursor temp" thisform.refresh()需要将文本框中输入的字符串作为select的课程名称的判断条件,由于涉及字符串的比较,因此alltrim语句必不可少,alltrim是将字符串前后空格去掉。 (4)表单初始化;init事件
public classtype do case case username="201801" thisform.Label1.Caption="唐同学的成绩单" case username="201802" thisform.Label1.Caption="孙同学的成绩单" case username="201803" thisform.Label1.Caption="朱同学的成绩单" case username="201804" thisform.Label1.Caption="沙同学的成绩单" otherwise endcase select score_st3 thisform.Grid1.recordsource="select score_st3.课程名称,成绩,; 课程属性,学分,绩点 from score_st3 into cursor temp" thisform.Grid1.refresh() go top为使初始化时,表格产生数据,重新初始化一次表格的数据源 (5)取消按钮:click事件 和表单初始化init事件一样,都是初始化,不再罗列代码。
表单中主要涉及表格、命令按钮、标签的使用。 (1)其中课表预览按钮:click事件 report form course_st1 to print preview 使用VFP专门用来预览表格的语句,较为方便 (2)课表打印按钮;click事件 report form course_st1 to printer noconsole 同样,调用VFP中专门用来打印表格的语句,由于涉及打印驱动的调用,此功能不能完整演示。 (3)表单初始化:init事件
loca for alltrim(id)=allt(username) if found() mes=alltrim(name) thisform.Label1.Caption=mes+"的课程表" else endif biao_name="course_st"+RIGHT(allt(username),1) thisform.grid1.recordsource="select "+biao_name+".* from "+ biao_name +" into cursor temp" thisform.refresh()根据username(id)值来判断登陆者的身份,利用loca for和if found语句连用,依次检索user信息表中的记录,查找相应的id号,并借助right函数提取id字符串的最后一位数字信息完成标签的动态命名。 表格同样利用SQL生成数据源,cursor产生临时表储存数据。
(1)查询按钮:click事件
select user loca for alltrim(id)=allt(thisform.text1.value) if found() user_name=allt(name) else messagebox("用户不存在!",64+0,"操作提示") thisform.text2.value="" return endif biao_name="course_st"+RIGHT(allt(thisform.text1.value),1) thisform.grid1.recordsource="select "+biao_name+".* from "+ biao_name +" into cursor temp" thisform.text2.value=user_name根据输入文本框中的字符串得到学号信息,利用SQL查询得到相应同学的课程表,同时将查询到的同学姓名赋给文本框,得到登陆者的信息。
(1) 查询按钮:click事件
if thisform.Optiongroup1.value=1 &&学号 biao_name="score_st"+RIGHT(allt(thisform.text1.value),1) thisform.grid1.recordsource="select "+biao_name+".* from "+ biao_name +" into cursor temp" else &&姓名 loca for alltrim(name)=allt(thisform.text1.value) if found() user_id=alltrim(id) biao_name="score_st"+RIGHT(allt(user_id),1) thisform.grid1.recordsource="select "+biao_name+".* from "+ biao_name +" into cursor temp" else endif endif需要利用do case语句进行选项按钮组的条件判断:同样通过输入模式,直接或间获取学号信息,利用right函数获得最后一位数字信息,赋值给新表姓名,以此来进行表格数据源的赋值。
(1)删除按钮:click事件
if allt(name)="老师" messagebox("老师是系统管理员,不能删除!",16,"操作提示") retu endif mes="确要删除 "+allt(name)+" 这条记录?" yn=messagebox(mes,1+32+0,"操作提示") if yn=1 *!* thisform.Grid1.recordsource="" delete messagebox("删除成功,需要重新进入表格查看",64+0,"操作提示") *!* pack *!* thisform.Grid1.recordsource="user" if eof() go bottom endif thisform.Grid1.refresh() &&刷新后台改变的变量 endif return 需要注意的一点是在click事件先进行delete删除标记,再进行物理删除,同时由于pack涉及文件的独占,因此再表单的destroy事件应设置文件的独占。 use user excl pack use(2)增加功能涉及另外一个表单,表单涉及如下: (1)确认按钮:click事件
o=thisform id_=o.text1.value password_=o.text2.value name_=o.text3.value class_=o.text4.value insert into user value(id_,password_,name_,class_) ***如果以下表单未打开则出错(错误号为1924) userform.refresh() thisform.release() return获取文本框中的信息,并利用insert进行表中记录的插入(从最后一行)。
(1)登录按钮:click事件
sele user o=thisform if empt(o.textname.text) messagebox("用户名不能为空!",48+0+0,"操作提示") o.textname.setfocus() retu endif loca for allt(id)=allt(o.textname.text); .and. allt(password)=allt(o.textcode.text) if found() username=allt(id) *username变量以后要用到对菜单的控制 thisform.release do form mainform else messagebox("用户名错或密码错!",48+0+0,"操作提示") *选中姓名文本 o.textname.selstart=0 o.textname.sellength=len(allt(o.textname.value)) o.textname.setfocus() endif retu定义全局变量username,接收登录者的id;通过locate for 和if found()语句判断用户登录id和姓名是否在系统中,只有两者同时识别,用户才能登录。
确认按钮:click事件
sele user o=thisform kl1_=o.text2.value kl_=o.text3.value if kl1_=kl_ repl password with kl_ for allt(id)=allt(username) o.release() messagebox("成功更改密码!",64+0,"操作提示") else messagebox("输入的密码有误?",48+0+0,"操作提示") o.text3.setfocus() endif retu(1)学生菜单设计如下: (2)老师菜单设计如下: 主菜单下设子菜单时,需把结果类型定义为子菜单”,而子菜单要执行某些表单时,需要设置结果类型为“命令”或“过程”,并执行相应语句do form 表单名
(1)主程序
public username,infotalk,infomark,infomark_buf infomark=1 infomark_buf=1 ***出错时统一转出错处理程序 on error do myerr ***将屏幕设置成不可见 _screen.visible=.f. ***调用登陆窗口 do form loginform.scx ***以下语句不能省,该语句为事件循环,若没有则程序连编后不能正常运行 read events(2)err程序设计
case error()=1582 =messagebox("计算结果不满足规则表达式!"+chr(13)+"错误号为:"+str(error()),16,"操作提示") case error()=1539 =messagebox("该商品已有入库或销售!不能删除!"+chr(13)+"错误号为:"+str(error()),16,"操作提示") case error()=1884 =messagebox("主关键字不能重复!"+chr(13)+"请重新输入!或退出后重新输入!"+chr(13)+"错误号为:"+str(error()),16,"操作提示") case error()=111 *如果数据库及表设置为只读状态时出现此错误,去除只读状态即可 =messagebox("数据库及表是只读状态!"+chr(13)+"请去除只读状态!"+chr(13)+"错误号为:"+str(error()),16,"操作提示") case error()=110 *如果表没有独占打开,则不能物理删除记录 =messagebox("表没有独占打开!"+chr(13)+"请独占打开表!"+chr(13)+"错误号为:"+str(error()),16,"操作提示") other =messagebox("其他错误,错误号:"+str(error()),16,"操作提示") ***2005号错误是字段名描述不正确 ***如果一个表单未打开则出错,错误号为1924。 ***13号错误,选择的工作区没有,12号错误指定的字段不存在 ***1号错误,指定运行的表单不存在 endcase return再次说明下公告的功能:老师系统界面发布公告,确认发布后,学生系统界面会受到“公告已更新,请查看!”。若公告未更新,再次登录学生系统,就会提示“公告未更新”。 (1)首先创建一个新表存放发布公告信息以及学生是否查看公告的标志位。 id字段存放学生学号,whether为逻辑性,老师公布公告时,初始化为.T.,学生在查看公告信息时,逻辑值变为.F.;mes设为字符型,存放公告信息,宽度值设为200根据公告长度定义。 (3) 老师发布公告设计: (1)发布按钮:click事件
select info_sys replace mes with thisform.edit1.value &&编辑公告标记,infotalk 接收字符串 do while not eof() replace whether with .T. skip &&指令指向下一条 enddo thisform.refresh() messagebox("公告发布成功!",64+0,"操作提示")将文本框中的字符串更新表中的字段mes,同时将学生是否查看公告标志位统一设置为.T. (2)学生接收公告设计 主界面init事件:
public stepval stepval=5 if username="2000" do tecd.mpr with this,.t. thisform.Label3.visible=.f. thisform.text1.visible=.f. else do stcd.mpr with this,.t. thisform.Label3.visible=.t. thisform.text1.visible=.t. endif if username#"2000" sele info_sys loca for allt(id)=allt(username) if whether=.T. &&公告更新 messagebox("有新公告,请查看!",64+0,"操作提示") replace whether with .F. else messagebox("没有新公告",64+0,"操作提示") endif endif select info_sys go top thisform.text1.value=mes &&显示公告 thisform.refresh()提示框需要根据登陆者的信息来判断是否显示,通过设置属性visible为.t.或.f.;通过获取登陆者的学号信息,判断公告是否更新标志位的值,来发布相应公告信息。
在利用VFP做项目的过程中,总会遇到许多问题,自己的体会是:遇到一个问题,解决一个问题,拖到最后会无法识别问题来源。并且还要记录解决方法,防止再次遇到。VFP设计项目时,各表单,各菜单都是有关联的,可以分模块去解决,留出与其他模块的接口,比如接收值的变量或者表单的名称等。在现在看来,VFP虽然比较落后,但对于目前开发仍有帮助,SQL语句在VFP中仍然可以使用,通过这次项目设计,使自己对数据库有较深刻的理解,以后会通过其他IDE,如my sql继续自己的数据库学习之路。
(1)表格的动态刷新: 自己是将数据加载到表格呈现,但在查询时需要将查询结果存到新表中,并将新表重新加载到表格的数据源中,这样就会产生一个问题,项目运行过程中,总会弹出提示:“是否重新覆盖原表数据”,影响项目实现的效果。 经查找资料得知,多条件查询时,有三种利用SQL-SELECT语句实现查询;利用参数化试图设计查询表单;利用数据过滤器设计查询表单。我们选择的是利用SQL-SELECT语句实现,步骤如下: 表格的reordsourcetype=”4-SQL说明”,即设置表格的数据源为SQL类型语句; Form的init: Thisform.grid1.recordsource=“sele 表名.字段名 from 表名 where 条件 into cursor temp” 假设在查询按钮的Click中: Thisform.grid1.recordsource=“sele 表名.字段名 from 表名 where 条件(功能条件) Into cursor temp” Thisform.refresh (2)系统交互公告功能实现。 设计过程中,遇到的主要问题是无法保存上次公告信息以及学生是否查看公告标志位,为此,我们建立一个新表来进行信息保存,由于表中数据是固定的,尽管系统初始化,表中信息却不会发生改变。
项目设计过程中,遇到问题较多,下面针对几个重点的说明: (1)VFP项目管理器是需要连编才能进行的,因此一旦各个表单,菜单之间建立联系,需要保证设计的完整性,不能单独进行编译,否则就会因缺少相关联系而报错,举例,若表单中用到全局变量,则不能运行表单,会发错误“找不到全局变量”,因此需要进行连编。 (2)我们在使用PACK语句时,常常需要解决文件独占的问题,只需要在相应表单里进行use 表名 exclusive即可。 (3)注意在使用“=”时,系统是进行模糊判别的,即if”2018”=”201801”系统默认这个表达式的逻辑值是真,只需要将”=”改为“==”即可。 (4)主界面表单应设置ShowWindow为2-作为顶层表单,而其他表单应设置其相应属性为1-在顶层表单中,如果设为0-在屏幕中,注意不要将_screen.visible属性设为.f.,否则表单会跟着屏幕不可见。 (5)删除表格数据出现空白时,可以先将表格的数据源清空,物理删除表中记录后,再将表赋值给表格的数据源。