第二個MFC例項:GPA計算器

weixin_34126215發表於2015-07-09

一、目的:此文通過一個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.程式演示:

六、小結

  麻雀雖小,五臟俱全。

相關文章