主要界面:(未添加皮肤)
登陆界面:
主界面:
1.题目要求
用MFC和ACCESS数据库设计一个请假管理系统可以进行对员工请假的管理和对公司假期及国定假期的设定。
2.功能需求
2.1.系统管理
- 更换登录用户
- 退出
2.2.请假管理窗体
- 员工请假登记
- 删除请假记录
- 撤销员工假期
- 员工请假记录
- 部门请假记录
2.3.假期设置窗体
- 设定国定假日
- 公司策略设定(年假数)
- 设置公司部门(增加、删除、修改)
- 管理员工信息(增加、删除、修改)
2.4.数据库管理
- 数据库备份
- 数据库恢复
3. 总体设计
3.1 系统模块
3.1.1请假管理系统功能模块图如下:
3.2 系统业务处理流程
3.2.1以管理员身份为例,请假管理系统流程图如下:
3.3 数据库设计
这里使用ACCESS2003数据库,数据库名字为StaffDBQ.mdb,其中包括7个表,如下:
表1:already
字段名称 | 数据类型 | 说明 |
ID | 文本 | 员工编号 |
day | 数字 | 员工已用年假数 |
表2:LeaveRecords
字段名称 | 数据类型 | 说明 |
ID | 文本 | 员工编号 |
name | 文本 | 员工姓名 |
reason | 文本 | 请假原因 |
day | 数字 | 请假天数 |
start | 日期/时间 | 开始时间 |
end | 日期/时间 | 结束时间 |
yearday | 是/否 | 申请年假 |
dept | 文本 | 所属部门 |
time | 数字 | 第几次请假 |
表3:SetCompany
字段名称 | 数据类型 | 说明 |
allday | 数字 | 年假天数 |
post | 文本 | 职位 |
表4:SetCountry
字段名称 | 数据类型 | 说明 |
name | 文本 | 假日名称 |
day | 数字 | 放假天数 |
start | 日期/时间 | 开始时间 |
end | 日期/时间 | 结束时间 |
表5:SetDepartment
字段名称 | 数据类型 | 说明 |
deptname | 文本 | 部门名称 |
表6:StaffInfo
字段名称 | 数据类型 | 说明 |
ID | 文本 | 员工编号 |
name | 文本 | 员工姓名 |
sex | 文本 | 员工性别 |
age | 数字 | 员工年龄 |
tel | 文本 | 员工电话 |
addr | 文本 | 员工住址 |
dept | 文本 | 员工部门 |
post | 文本 | 员工职位 |
表7:user
字段名称 | 数据类型 | 说明 |
username | 文本 | 用户名称 |
password | 文本 | 用户密码 |
right | 数字 | 用户权限 |
4.详细设计
在这次的请假管理系统课程设计中,由于很多界面实现的原理都差不多,所以就不把每个界面是怎么实现的一一描述出来了,只是详细的描述要怎么实现某个效果。
4.1数据库的自动关联
为了避免在别的电脑上面运行要手动关联数据库的麻烦,我们这里自动关联数据源。代码添加位置:App类的InitInstance()函数中。
//自动关联数据源
SQLConfigDataSource(NULL,ODBC_ADD_DSN,
"Microsoft Access Driver(*.mdb)",
"DSN=StaffDSN\0"
"DBQ=.\\StaffDBQ.mdb\0"
);
这里用到了SQLConfigDataSource()函数,其中StaffDSN为数据源名字,StaffDBQ.mdb为数据库名字。注意用此函数的时候,需要添加头文件#include"odbcinst.h"同时在工程->设置中添加odbccp32.lib链接库。
4.2登陆功能
程序一运行会首先进入App类的初始化函数InitInstance()中,所以我们在该函数中弹出登陆界面。
//用户登陆
CLoginDlgloginDlg;
if(loginDlg.DoModal()!=IDOK)
return FALSE;
DoModal()会返回点击按钮的ID值,登陆原理:在登陆界面的登陆按钮(IDOK)下面添加登陆验证代码,首先检测账号是否为空,再检测密码是否为空。如果都不为空,则查找数据库里面的账号密码是否匹配。如果匹配,传递用户的权限,进入系统。
voidCLoginDlg::OnOK()
{
CUserSet recordset;
CString strSQL;
UpdateData(TRUE);
if(m_strUser.IsEmpty())
{
MessageBox("请输入用户名!","登陆错误");
m_ctrUser.SetFocus();
return;
}
//检查密码是否输入
if(m_strPass.IsEmpty())
{
MessageBox("请输入密码!","错误");
m_ctrPass.SetFocus();
return;
}
CStaffVactionApp* ptheApp = (CStaffVactionApp *) AfxGetApp();
strSQL.Format("select * from userwhere username='%s' AND password='%s'",m_strUser,m_strPass);
if(!recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
{
MessageBox("打开数据库失败!","数据库错误",MB_OK);
return;
}
if(recordset.GetRecordCount()==0)
{
recordset.Close();
MessageBox("密码错误,请重新输入!","错误");
m_strPass="";
m_ctrPass.SetFocus();
UpdateData(FALSE);
}
else
{
ptheApp->m_nRight =recordset.m_right; //传递权限
recordset.Close();
CDialog::OnOK();
}
//CDialog::OnOK();
}
4.3如何给下拉控件添加下拉列表
这里就拿用户登陆界面的下拉列表来分析,效果如下图:
由于要在对话框一弹出来就有下拉列表,所以需要将添加下拉列表的代码添加在对话框的初始化函数OnInitDialog()中。如果没有这个函数,可以在类向导里面添加WM_INITDIALOG消息响应事件。
BOOLCLoginDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//将登陆用户名加载到列表下拉框
CUserSet recordset;
CString strSQL;
UpdateData(TRUE);
strSQL="select * from user";
if(!recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
{
MessageBox("打开数据库失败!","数据库错误",MB_OK);
return FALSE;
}
while(!recordset.IsEOF())
{
m_ctrUser.AddString(recordset.m_username);
recordset.MoveNext();
}
recordset.Close();
return TRUE; // return TRUE unless you set the focus to acontrol
// EXCEPTION: OCX Property Pagesshould return FALSE
}
其中m_ctrUser是下拉控件通过类型导关联的一个变量,m_username为数据库里面用户的名字,这里主要用到AddString()函数往下拉控件里面添加下拉项。
4.4如何在列表控件中加载列表
这里用设定国定假日来举例,添加列表后,效果如下图:
首先在对话框上拖放一个列表控件,然后点击->属性->样式->查看(报告)->排列(顶端)。
由于要在对话框产生的时候就显示列表,同样要将代码添加在对话框的初始化函数OnInitDialog()中。
BOOLCSetCountryDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CSetCountrySet m_recordset;
CString strSQL;
UpdateData(TRUE);
strSQL="select * fromSetCountry";
if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
{
MessageBox("打开数据库失败!","数据库错误",MB_OK);
return FALSE;
}
while(!m_recordset.IsEOF())
{
m_ctrName.AddString(m_recordset.m_name); //添加假日名称下拉列表数据
m_recordset.MoveNext();
}
m_recordset.Close(); //关闭记录集
//添加左边列表栏数据
//创建用户列表
m_ctrListName.InsertColumn(0,"假日名称");
m_ctrListName.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);
m_ctrListName.SetColumnWidth(0,110); //第0列宽度110
//在添加用户列表中添加用户名
RefreshData();
return TRUE; // return TRUE unless you set the focus to acontrol
// EXCEPTION: OCX Property Pagesshould return FALSE
}
voidCSetCountryDlg::RefreshData()
{
m_ctrListName.SetFocus(); //设置控件焦点
m_ctrListName.DeleteAllItems(); //清空用户列表
m_ctrListName.SetRedraw(FALSE); //重绘
CString strSQL;
UpdateData(TRUE);
//打开记录集
strSQL="select * fromSetCountry";
if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
{
MessageBox("打开数据库失败!","数据库错误",MB_OK);
return ;
}
//添加用户名到用户列表中
int i=0;
while(!m_recordset.IsEOF())
{
m_ctrListName.InsertItem(i++,m_recordset.m_name); //往列表中添加信息InsertItem插入行
m_recordset.MoveNext();
}
m_recordset.Close(); //关闭数据库
m_ctrListName.SetRedraw(TRUE); //重绘
}
先要创建上面的列表项标记栏目,通过InsertColumn()函数来插入一列,其中第一个参数表示第几列,如果要往后面追加列,则将第一个参数+1,用函数SetColumnWidth来设定列宽度。然后开始用InsertItem()函数插入行。
注意由于列表的插入和删除可能会造成闪屏,这里用到SetRedraw()函数FALSE表示重绘列表但是不现实出来,然后通过TRUE一次显示出来,和俄罗斯方块里面的双缓冲差不多吧。
4.5如何实现列表栏的点击事件
这里我们拿公司年策略设定(年假数)来举例,如下图:
首先给列表控件添加一个点击事件,双击列表控件就可添加好函数了。
然后在该函数中编写如下代码:
voidCSetCompanyDlg::OnClickListUsergroup(NMHDR* pNMHDR, LRESULT* pResult)
{
CString strSQL;
UpdateData(TRUE);
//从数据库中获取选择用户名的资料
int i = m_ctrListUser.GetSelectionMark();
m_strPost = m_ctrListUser.GetItemText(i,0);
strSQL.Format("select * fromSetCompany where post='%s'",m_strPost);
if(!m_recordset.Open(AFX_DB_USE_DEFAULT_TYPE,strSQL))
{
MessageBox("打开数据库失败!","数据库错误",MB_OK);
return ;
}
//显示用户资料
m_strPost = m_recordset.m_post;
m_nOldYear = m_recordset.m_allday;
m_nNewYear = m_recordset.m_allday;
m_recordset.Close();
UpdateData(FALSE);
*pResult = 0;
}
这里用到GetSelectionMark()函数,函数的返回值为你当前点击的行数。列表栏第一行为0。比如:你点击了“管理员”它会返回一个值为0。然后你通过GetItemText()函数来获得列表栏中这一行的文字是什么。最后将这个值传递到数据库中查询,从而显示出里点击的信息。
4.6数据库字段的查询
这里用撤销员工请假记录来举例,如下图:
当我们输入编号,然后点击查询,则执行数据库信息的查询,我们这里查询的原理是通过员工的ID和员工是第几次请假来进行查询,代码如下:
voidCCancelLeaveDlg::OnButtonFind()
{
CString strSQL;
UpdateData(TRUE);
//从数据库中获取选择用户名的资料
//显示用户第一次请假资料
m_recordset.Open();
if(!m_recordset.IsEOF())
m_recordset.MoveLast();
while(!m_recordset.IsBOF() &&m_recordset.m_ID != m_strFind)
{
m_recordset.MovePrev();
}
if (m_recordset.IsBOF())
{
m_recordset.Close();
MessageBox("未找到该员工请假记录","未找到",MB_OK);
return;
}
m_strID = m_recordset.m_ID;
m_strName = m_recordset.m_name;
m_nTime = m_recordset.m_time;
m_tmStart = m_recordset.m_start;
m_tmEnd = m_recordset.m_end;
m_strReason = m_recordset.m_reason;
m_nDay = m_recordset.m_day;
if (m_recordset.m_yearday)
{
CButton* pBtn =(CButton*)GetDlgItem(IDC_CHECK_YEAR);
pBtn->SetCheck(1);
}
else
{
//置空年假复选框
CButton* pBtn =(CButton*)GetDlgItem(IDC_CHECK_YEAR);
pBtn->SetCheck(0);
}
m_recordset.Close();
UpdateData(FALSE);
}
首先用Open()函数打开数据库,如果记录集不是空,则将数据库的记录集移动到最后一条MoveLast(),然后通过MovePrev()函数依次往前找,因为要撤销请假肯定是数据库里面记录的最后一次请假信息。
另外,也可以用SQL语句来查询,比如登陆界面账号密码匹配查询的时候,构造SQL语句如下:
strSQL.Format("select* from user where username='%s' AND password='%s'",m_strUser,m_strPass);
这句话表示从user这个表里面查询账号(username)为m_strUser,密码(password)为m_strPass的那条记录集。其中username,password为表中字段的名称。m_strUser为对话框编辑框关联的变量。最后通过GetRecordCount()函数来判断有没有找到,当GetRecordCount() == 0 的时候说明没有找到。
4.7数据库和MFC时间问题
这个问题纠结了我一段时间,主要在实现员工请假界面的时候,由于设定了国定假日,所以每次员工请假的时候要搜索是否在国定假日期间。
比如:10月1日-10月7日是国庆节,属于国定假日。当一个员工在9月29日(9月有30天)请假4天的时候,30- 1,2,3,4,5,6,7- 8,9,10。那么他结束日期应该在10月10号。如果在10月2号请假4天呢?10月6号请假4天呢?这些不难实现。主要在一个时间比较上面。我数据库中的时间格式是2014/01/08 21:51:12 和软件上面的时间格式是匹配的,但是我们设定国假的时候,肯定不能直接读取界面上面的时间,而要稍微处理一下,将后面的时间变成23:59:59
由于时间不可以减,我很笨,就这样实现:
//将时间加到23小时59分59秒
while (!(m_tmStart.GetHour() == 23&& m_tmStart.GetMinute() == 59 && m_tmStart.GetSecond() == 59))
{
m_tmStart += 1;
}
while (!(m_tmEnd.GetHour() == 23 &&m_tmEnd.GetMinute() == 59 && m_tmEnd.GetSecond() == 59))
{
m_tmEnd += 1;
}
然后构造SQL语句进行查询,时间构造如下:
//格式化时间
strStart.Format("%4d/%02d/%02d%02d:%02d:%02d", \
m_tmStart.GetYear(),m_tmStart.GetMonth(),m_tmStart.GetDay(),\
m_tmStart.GetHour(),m_tmStart.GetMinute(),m_tmStart.GetSecond());
strEnd.Format("%4d/%02d/%02d%02d:%02d:%02d", \
m_tmEnd.GetYear(),m_tmEnd.GetMonth(),m_tmEnd.GetDay(),\
m_tmEnd.GetHour(),m_tmEnd.GetMinute(),m_tmEnd.GetSecond());
strSQL.Format("select * fromSetCountry where start >= #%s# and start <= #%s# and end >=#%s#",\
strStart,strEnd,strEnd);
其中注意:年月日的位数。以及时间查询的格式:##双井号,不要引号。
4.8数据库的增加,删除,修改操作
这个很简单,值得注意的几点是:
- 在数据库表打开后一定要记得关闭。.Open().Close()
- 在执行增加删除修改之前一定要是数据库处于可编辑状态,而不只是打开。也就是要记得调用m_pSet->AddNew()或者m_pSet->Edit()。
4.9数据库的备份与恢复
voidCMainFrame::OnMenuDbad()
{
if(AfxMessageBox("您确定要备份数据库吗?",MB_OKCANCEL)==IDCANCEL)
{
return;
}
if(CopyFile(".\\StaffDBQ.mdb",".\\StaffDBQ.bak",FALSE))
AfxMessageBox("数据库备份成功!");
else
AfxMessageBox("数据库备份失败!");
}
voidCMainFrame::OnMenuDbre()
{
if(AfxMessageBox("还原数据库将覆盖原来的数据库。您确定要还原吗?",MB_OKCANCEL)==IDCANCEL)
{
return;
}
if(CopyFile(".\\StaffDBQ.bak",".\\StaffDBQ.mdb",FALSE))
AfxMessageBox("数据库还原成功!");
else
AfxMessageBox("数据库还原失败!");
}
5 测试与实现
5.1登陆界面
5.2程序主界面
5.3请假管理窗体
5.4假期设置窗体
5.5设定国定假日
5.6公司策略设定(年假数)
5.7删除请假记录
5.8按部门汇总某段时期内的请假记录
5.9详细列出某个员工某段时期内的所有请假记录
5.10撤销假期
其它附属界面:
a. 增加公司部门
b. 删除公司部门
c. 修改公司部门
d. 添加新员工
e. 删除员工信息
f. 修改员工信息
g. 数据库的恢复与数据库的备份
6 总结
首先总结一下编写过程中遇到的一些问题吧!
问题一:删除信息后列表框跟新不及时;
问题出现的原因:定义记录集对象为局部对象;
解决方法:将记录集对象定义在类的头文件里。
问题二:程序运行到App类的初始化函数InitInstance()中的
if(!ProcessShellCommand(cmdInfo))语句时,出现如下错误:
问题出现的原因:我开始手动增加了一个表中的一个字段,然后增加映射的时候没有指定表加错了,[already]为一个表名。
解决方法:
更改前:
RFX_Text(pFX, _T("[ID]"), m_ID);
RFX_Int(pFX, _T("[day]"), m_day);
更改后:
RFX_Text(pFX,_T("[already].[ID]"), m_ID);
RFX_Int(pFX,_T("[already].[day]"), m_day);
问题三:将程序从release版本更换到debug版本后,我跟踪运行到:
if(!pFrame->LoadFrame(m_nIDResource,
WS_OVERLAPPEDWINDOW| FWS_ADDTOTITLE, // default framestyles
NULL,&context))
函数时出行错误。
问题出现的原因:调试里面有的断言宏出了问题
解决方法:找了好久在CSDN上面看到是对话框出现了问题,
其中这两个“√”是我手残没事干勾上去的,导致了错误!去掉就OK了。
问题四:无效游标
问题出现原因:我分析了一下,可能多个表打开关闭嵌套之中的一些问题。
解决方法:我将每个表的打开位置、关闭位置分别标记一下,挪动了几行代码的位置,解决了问题。
问题五:给列表框更换不了背景颜色
问题出现原因:单独建立一个工程,在View类响应了WM_CTLCOLOR事件,添加如下代码:
HBRUSH CSetCountryDlg::OnCtlColor(CDC* pDC, CWnd*pWnd, UINT nCtlColor)
{
HBRUSH hbr =CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
if (nCtlColor== IDC_LIST1)
{
pDC->SetBkMode(TRANSPARENT);
returnm_hbrush;
}
return hbr;
}
列表背景颜色改变成功了,但是在我的程序里面却改变不了,连if()语句都没有进来。可能是消息被拦截了。
解决方法:至今未解决。
分析程序的优缺点:
优点就是功能全部实现了,可能这也是唯一的优点了,因为自己的代码基本上就是来自Library,没什么创新和值得一提的地方,尤其是VC60的界面实在是不敢恭维。本来是添加了一个皮肤的后来感觉皮肤不好看又删掉了,就用原版的好了。
说一下,这次VC程程设计的感受吧,首先值得高兴的是,ACCESS数据库的操作基本上是熟练了,回去有时间的时候帮社联写一个答题抽奖系统,本来去年就问我让我写了,我捣鼓了好久,就是不会用数据库。然后就是感觉,课程设计真的搞的很累。基本上每天早上9:00就去18#然后就和他(她)们一起讨论,一直到晚上9:00才回宿舍,可以说是早出晚归了。总之,VC课程就这么愉快结束了,很感谢贾老师带我们的这学期,我学到很多!
源代码下载网盘:链接: http://pan.baidu.com/s/1dDDYS2d 密码: xq1b (包括 代码+数据库+报告+流程图+模块图)
转载请保留原文地址:http://blog.csdn.net/u013055228/article/details/18056169