一、目的:此文通過一個GPA計算器的制作,介紹基於對話框的應用程序的編程方法、常用控件的編程技巧以及控件外觀的更改技巧。
二、功能描述:所謂GPA計算器,即進行GPA換算。
功能要求由如下幾點:
1.學生成績可以由文件導入。
2.GPA標准可以選擇,也可以手動輸入。
3.GPA的計算過程易於監督控制。
4.計算結果的可讀性強。
三、關鍵技術與算法:
1.CFile和CArchive
CFile是MFC的文件操作基本類,它直接支持無緩沖的二進制磁盤I/O操作,並通過其派生類支持文本文件、內存文件和Socket文件。
打開文件方法:
CFile(LPCTSTR lpszFileName, UINT nOpenFlags);
其中,lpszFileName表示要打開的文件名,可以是相對路徑或絕對路徑。
一個CArchive對象與一個文件相連,充當了文件與要讀寫的數據結構之間的橋梁和管道的角色。它在文件和要讀寫的數據結構之間設置了一個緩沖區,提供數據緩沖機制。CArchive的構造函數為:
CArchive(CFile *pFile, UINT nMode, int nBufSize=4096, void *lpBuf=NULL)
其中,pFile表示CArchive所基於的文件,nMode制定CArchive的存取模式。
本實例用上述兩個類完成了對成績文件的讀取。
2.公共對話框
本實例用到了CFileDialog,它是文件對話框,提供從磁盤目錄結構中選擇一個文件的對話框界面(經常用於打開文件或保存文件)。
四、程序實現:
1.創建項目:項目名為GPACalculator,基於對話框,其他默認。
2.界面設計:打開對話框資源IDD_GPACALCULATOR_DIALOG,該對話框將是程序運行時的主界面。
2.1 下面按要求添加控件:
控件類型 ID 屬性設置
Button IDC_BUTTON_OPEN Caption設為“瀏覽...”
Button IDC_BUTTON_OK Caption設為“統計”
Static Text 默認 Caption設為“參比標准”
Static Text 默認 Caption設為“標准分”
Static Text 默認 Caption設為“原始分”
Static Text 默認 Caption設為“統計信息”
Static Text 默認 Caption設為“分數列表”
Static Text 默認 Caption設為“加權平均分”
Static Text 默認 Caption設為“GPA”
Static Text 默認 Caption設為“總學分”
Edit Box IDC_EDIT_PATHNAME 默認
Edit Box IDC_EDIT_S1 ReadOnly
Edit Box IDC_EDIT_S2 ReadOnly
Edit Box IDC_EDIT_S3 ReadOnly
Edit Box IDC_EDIT_S4 ReadOnly
Edit Box IDC_EDIT_S5 ReadOnly
Edit Box IDC_EDIT_S6 ReadOnly
Edit Box IDC_EDIT_S7 ReadOnly
Edit Box IDC_EDIT_O1 ReadOnly
Edit Box IDC_EDIT_O2 ReadOnly
Edit Box IDC_EDIT_O3 ReadOnly
Edit Box IDC_EDIT_O4 ReadOnly
Edit Box IDC_EDIT_O5 ReadOnly
Edit Box IDC_EDIT_O6 ReadOnly
Edit Box IDC_EDIT_O7 ReadOnly
Edit Box IDC_EDIT_O8 ReadOnly
Edit Box IDC_EDIT_AVERAGE ReadOnly
Edit Box IDC_EDIT_GPA ReadOnly
Edit Box IDC_EDIT_TOTAL ReadOnly
Group Box 默認 Caption設為“第一步:載入分數文件”
Group Box 默認 Caption設為“第二步:設置參比標准”
Group Box 默認 Caption設為“第三步:統計並顯示”
Combo Box IDC_COMBO_STANDARD DropList
List Box IDC_LIST_SCORE 默認
看下效果圖:
這里需要說明幾點:
1>控件配置表與圖片還是比較好對應的。兩個按鈕及一些靜態文本框及三個組框、一個組合框、一個列表框,編輯框有一堆:第一個對應第一步里的那個pathname,然后IDC_EDIT_Sx對應標准分的那七個框框,IDC_EDIT_Ox對應原始分的那八個框框,后面是第三步里的三個編輯框,對應清晰就ok了。
2>至於那個List Box,默認屬性里不選擇sort(分類),即對於列表框的數據不分類排序。
3>這里得提點下Combo Box的用法。注意在設計ComboBox時,點下向下箭頭(小三角形),即(注意看那藍色的小正方形是否空心):
然后將控件下邊(那個實心的小正方形)向下拉,即:
這樣才能顯示出ComboBox的下拉的項。
2.2 打開ClassWizard,針對IDD_GPACALCULATOR_DIALOG所指向的類CGPACalculatorDlg,為控件添加類成員變量,按下面的要求:
控件ID 變量名 數據類型 IDC_EDIT_PATHNAME m_sPathName CString IDC_EDIT_S1 m_dS1 double IDC_EDIT_S2 m_dS2 double IDC_EDIT_S3 m_dS3 double IDC_EDIT_S4 m_dS4 double IDC_EDIT_S5 m_dS5 double IDC_EDIT_S6 m_dS6 double IDC_EDIT_S7 m_dS7 double IDC_EDIT_O1 m_dO1 double IDC_EDIT_O2 m_dO2 double IDC_EDIT_O3 m_dO3 double IDC_EDIT_O4 m_dO4 double IDC_EDIT_O5 m_dO5 double IDC_EDIT_O6 m_dO6 double IDC_EDIT_O7 m_dO7 double IDC_EDIT_O8 m_dO8 double IDC_EDIT_AVERAGE m_strAverage CString IDC_EDIT_GPA m_strGPA CString IDC_EDIT_TOTAL m_dTotal double IDC_COMBO_STANDARD m_cmbStandard CComboBox IDC_LIST_SCORE m_ListScore CListBox
3.代碼編寫:
3.1 添加菜單控制
3.1.1 制作菜單:vc菜單->插入->資源->選擇menu,點擊新建->制作如下(選中虛線框,鼠標右鍵點擊屬性):
其中,“退出”的ID設為ID_MENUITEM_EXIT,“使用說明”的ID設為ID_MENUITEM_MAN,“關於”的ID設為ID_MENUITEM_ABOUT。菜單名字設為IDR_MAIN_MENU。
3.1.2 裝載菜單:打開主界面IDD_GPACALCULATOR_DIALOG的屬性對話框,在menu的列表框中選中相應的菜單資源。如下:
3.1.3 編寫菜單代碼。使用ClassWizard針對菜單項向對話框類中增加函數。
1>【使用說明】菜單:
void CGPACalculatorDlg::OnMenuitemMan() { // TODO: Add your command handler code here WinExec("notepad.exe EXPLAIN.HEP",SW_SHOW); }
2>【關於】菜單:
void CGPACalculatorDlg::OnMenuitemAbout() { // TODO: Add your command handler code here CAboutDlg dlg; dlg.DoModal(); }
3>【退出】菜單:
void CGPACalculatorDlg::OnMenuitemExit() { // TODO: Add your command handler code here DestroyWindow(); }
3.2 分數存儲結構
在GPACalculatorDlg.h里添加讀取分數的數據結構:
typedef struct stScore{ double dOPoint;//原始分 double dSPoint;//標准分 double dNum;//學分 }stScore;
在StdAfx.h中添加下列頭文件以支持CArray的使用:
#include <afxtempl.h>
至於位置,可以寫這里:
#include <afxwin.h> // MFC core and standard components #include <afxext.h> // MFC extensions #include <afxdisp.h> // MFC Automation classes #include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls #include <afxtempl.h> #ifndef _AFX_NO_AFXCMN_SUPPORT #include <afxcmn.h> // MFC support for Windows Common Controls #endif // _AFX_NO_AFXCMN_SUPPORT
在GPACalculatorDlg.h的類GPACalculatorDlg的定義中添加私有對象arScore,用作分數存儲:
CArray<stScore,stScore&> arScore;
3.3 為類GPACalculatorDlg添加一些自定義功能函數(都是私有的。在類GPACalculatorDlg.h文件里添加函數聲明,在類GPACalculatorDlg.cpp文件里添加函數實現):
1>GetSDPoint:根據參比標准,將原始分轉化為標准分。
double GetSDPoint(double dPoint);
double CGPACalculatorDlg::GetSDPoint(double dPoint){ UpdateData(); CArray<double,double&> arOPoint; CArray<double,double&> arSPoint; arOPoint.Add(m_dO1); arOPoint.Add(m_dO2); arOPoint.Add(m_dO3); arOPoint.Add(m_dO4); arOPoint.Add(m_dO5); arOPoint.Add(m_dO6); arOPoint.Add(m_dO7); arOPoint.Add(m_dO8); arSPoint.Add(m_dS1); arSPoint.Add(m_dS2); arSPoint.Add(m_dS3); arSPoint.Add(m_dS4); arSPoint.Add(m_dS5); arSPoint.Add(m_dS6); arSPoint.Add(m_dS7); if(dPoint>=arOPoint[0]) return arSPoint[0]; for(int i=1;i<arOPoint.GetSize();i++){ if(dPoint>=arOPoint[i]) return arSPoint[i-1]; } return 0; }
2>SpliterString:分數文件是以“原始分 學分”的方式給出的,需要一個功能函數來將這個字符串拆分成兩個double型的原始分和學分。
void SpliterString(CString str,double &a,double &b);
void CGPACalculatorDlg::SpliterString(CString str,double &a,double &b){ CString strOne,strTwo; int ifind; str.TrimLeft(' '); str.TrimRight(' '); ifind=str.Find(' '); strOne=str.Left(ifind); strTwo=str.Right(str.GetLength()-ifind-1); a=atof(LPCTSTR(strOne)); b=atof(LPCTSTR(strTwo)); }
3>GatherData:將分數文件的信息導入到分數存儲結構,當m_sPathName="",即無分數文件時,則導入失敗。
void GatherData();
void CGPACalculatorDlg::GatherData(){ CString str; stScore temp; if(m_sPathName=="") return; arScore.RemoveAll(); CFile file(m_sPathName,CFile::modeRead); CArchive ar(&file,CArchive::load);; ar.ReadString(str); while(str!=""){ SpliterString(str,temp.dOPoint,temp.dNum); temp.dSPoint=GetSDPoint(temp.dOPoint); arScore.Add(temp); ar.ReadString(str); } }
4>ChangeType:根據不同的輸入值,轉變成不同的參比標准。
void ChangeType(int nType);
void CGPACalculatorDlg::ChangeType(int nType){ switch(nType){ case 0: case 4: m_dS1=m_dS2=m_dS3=m_dS4=m_dS5=m_dS6=m_dS7=0; m_dO1=m_dO2=m_dO3=m_dO4=m_dO5=m_dO6=m_dO7=m_dO8=0; break; case 1: m_dS1=4; m_dS2=3; m_dS3=2; m_dS4=1; m_dS5=m_dS6=m_dS7=0; m_dO1=100; m_dO2=90; m_dO3=80; m_dO4=70; m_dO5=60; m_dO6=m_dO7=m_dO8=0; break; case 2: m_dS1=4.3; m_dS2=4; m_dS3=3.7; m_dS4=3.3; m_dS5=3.0; m_dS6=2.7; m_dS7=2.3; m_dO1=100; m_dO2=90; m_dO3=85; m_dO4=80; m_dO5=75; m_dO6=70; m_dO7=65; m_dO8=60; break; case 3: m_dS1=4; m_dS2=3; m_dS3=2; m_dS4=m_dS5=m_dS6=m_dS7=0; m_dO1=100; m_dO2=85; m_dO3=70; m_dO4=60; m_dO5=m_dO6=m_dO7=m_dO8=0; break; } }
5>SetEditRead:輸入為TRUE時,參比標准的編輯框將被設為只讀模式;而輸入為FALSE時,參比標准的編輯框將被設為可寫模式。
void SetEditRead(bool bReadOnly);
void CGPACalculatorDlg::SetEditRead(bool bReadOnly){ ((CEdit *)GetDlgItem(IDC_EDIT_S1))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_S2))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_S3))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_S4))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_S5))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_S6))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_S7))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_O1))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_O2))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_O3))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_O4))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_O5))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_O6))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_O7))->SetReadOnly(bReadOnly); ((CEdit *)GetDlgItem(IDC_EDIT_O8))->SetReadOnly(bReadOnly); }
3.4 控件事件:借助ClassWizard添加控件對應的處理函數。
1>組合框:可以通過下拉菜單選擇不同的參比標准。
需要做兩步:
首先,為組合框設初始值,否則下拉菜單里是空的。在CGPACalculatorDlg.cpp文件里的OnInitDialog()初始化函數里添加代碼為組合框設初始值:
BOOL CGPACalculatorDlg::OnInitDialog() { CDialog::OnInitDialog(); // IDC_COMBO_STANDRD m_cmbStandard.AddString("0"); m_cmbStandard.AddString("1"); m_cmbStandard.AddString("2"); m_cmbStandard.AddString("3"); m_cmbStandard.AddString("用戶自定義"); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); ......
然后,還需要為組合框添加處理函數:
void CGPACalculatorDlg::OnSelchangeComboStandard() { // TODO: Add your control notification handler code here int nIndex=m_cmbStandard.GetCurSel(); if(4==nIndex) SetEditRead(false); else SetEditRead(true); ChangeType(nIndex); UpdateData(false); }
2>【瀏覽...】按鈕:彈出“打開”對話框以供選擇磁盤上的分數文件。添加的處理函數如下:
void CGPACalculatorDlg::OnButtonOpen() { // TODO: Add your control notification handler code here CFileDialog fdlg(true,NULL,NULL,OFN_OVERWRITEPROMPT,"成績文件|*.dat;*.txt|",NULL); if(fdlg.DoModal()==IDOK){ m_sPathName=fdlg.GetPathName(); } UpdateData(false); }
3>【統計】按鈕:單擊該按鈕計算各項參數值,並把分數顯示在列表框。添加的處理函數如下:
void CGPACalculatorDlg::OnButtonOk() { // TODO: Add your control notification handler code here double dGPAall=0; double dAvgall=0; GatherData(); int i=0; int size=arScore.GetSize(); if(size==0) return; m_ListScore.ResetContent(); m_ListScore.AddString("序號 成績 學分 標准"); m_ListScore.AddString("-------------------------------------------"); m_dTotal=0; while(i<size){ CString str; m_dTotal+=arScore.GetAt(i).dNum; dGPAall+=arScore.GetAt(i).dSPoint*arScore.GetAt(i).dNum; dAvgall+=arScore.GetAt(i).dOPoint*arScore.GetAt(i).dNum; str.Format("%.2d %0.2f %0.2f %0.2f",i+1,arScore.GetAt(i).dOPoint,arScore.GetAt(i).dNum,arScore.GetAt(i).dSPoint); i++; m_ListScore.AddString(str); } m_strGPA.Format("%.2f",dGPAall/m_dTotal); m_strAverage.Format("%.2f",dAvgall/m_dTotal); UpdateData(false); }
3.5 界面外觀
若對界面控件默認的外觀感到不滿意,可以截取消息加以控制。本程序截取VM_CTLCOLOR消息來達到修改控件外觀顏色的目的。操作如下:
處理函數為:
HBRUSH CGPACalculatorDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); // TODO: Change any attributes of the DC here switch(pWnd->GetDlgCtrlID()){ case IDC_EDIT_S1: case IDC_EDIT_S2: case IDC_EDIT_S3: case IDC_EDIT_S4: case IDC_EDIT_S5: case IDC_EDIT_S6: case IDC_EDIT_S7: case IDC_EDIT_O1: case IDC_EDIT_O2: case IDC_EDIT_O3: case IDC_EDIT_O4: case IDC_EDIT_O5: case IDC_EDIT_O6: case IDC_EDIT_O7: case IDC_EDIT_O8: pDC->SetBkColor(RGB(255,255,255)); pDC->SetTextColor(RGB(0,0,255)); break; case IDC_LIST_SCORE: pDC->SetBkColor(RGB(255,255,255)); pDC->SetTextColor(RGB(0,0,255)); break; case IDC_EDIT_AVERAGE: case IDC_EDIT_GPA: case IDC_EDIT_TOTAL: pDC->SetBkColor(RGB(255,255,255)); pDC->SetTextColor(RGB(255,0,255)); } // TODO: Return a different brush if the default is not desired return hbr; }
五、運行結果:
1.使用說明:
1.1 GPA計算器要求文件輸入,輸入格式:[spaces]課程成績<spaces>學分數[spaces]
eg:
94 6.5 62 1.5 85 2.5 72 3 90 2 93 1.5 87 3 94 1.5
1.2 GPA計算器提供了幾種GPA參考分數標准,可通過下拉列表選擇。若程序中提供的標准(0/1/2/3)不能滿足需要的話,可選擇“用戶自定義”,可以自行在編輯框里填入自己所需的標准。
1.3 選擇分數文件和參比標准后,單擊【統計】按鈕即可求出相應的加權平均分、GPA及總學分等。程序會顯示分數文件中的分數,可據此核對計算結果是否正確。
2.程序演示:
六、小結
麻雀雖小,五臟俱全。