員工請假管理系統(MFC+ACCESS資料庫+ODBC資料來源)
主要介面:(未新增面板)
登陸介面:
主介面:
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課程就這麼愉快結束了,很感謝賈老師帶我們的這學期,我學到很多!