MFC模拟高尔顿板实验

先上效果图,图中小球是动态下落的,下落到对应桶里会更新相应计数。

《1》建立基于单文档的应用程序,名为:GEDB。

《2》为类CDEDBView添加成员函数void CGEDBView::DrawFrame(CDC *pDC),并添加如下代码:

void CGEDBView::DrawFrame(CDC *pDC)

{

         CRect rc;//定义一个矩形区域变量

         GetClientRect(rc);//获取当前窗口的客户区大小

         CPoint point[12] ;

         point[0] = (85 ,5) ;//画图区域的左上角

         point[1] = (685 ,403) ;//画图区域的右下角

        

         //设置每个钉子的半径为12

         int R = 12 ;

         //绘制图形区域的边缘(灰色)

         CPen pen(PS_INSIDEFRAME,1,RGB(192,192,192));//定义画笔

         CPen* oldPen=pDC->SelectObject(&pen); //pen选入设备环境

         CRect recttempt(85,5,685,403) ;

         pDC->Rectangle(recttempt) ;

        

         //绘制显示小球的所在区域

         CRect rect1(85 -4*R ,300-23*R ,85-2*R ,300+7*R) ;

         pDC->Rectangle(rect1) ;

         pDC->SetBkColor(RGB(250,250,0)) ;

         CString str[2]= {"顶部","底部"};

         pDC->TextOut(85 -4*R,300-25*R,str[0]) ;

         pDC->TextOut(85 -4*R,300+8*R,str[1]) ;

         //绘制不同桶间的分割线(蓝色)

         CPen Lpen0 ;

         Lpen0.CreatePen(PS_SOLID ,2 ,RGB(0,0,255)) ;

         pDC->SelectObject(Lpen0) ;

         for (int i = 1 ;i<12 ;i++)

         {

                   pDC->MoveTo(100 + i*4*R ,400) ;

                   pDC->LineTo(100 + i*4*R ,300) ;

         }

        

         //绘制高尔顿版的边缘(肖贡土色)

         CPen Lpen1 ;

         Lpen1.CreatePen(PS_DASH ,5 ,RGB(197,97,20)) ;

         pDC->SelectObject(Lpen1) ;

         pDC->MoveTo(100,400) ;

         pDC->LineTo(100 ,300) ;

         pDC->MoveTo(676,400) ;

         pDC->LineTo(676 ,300) ;

         pDC->MoveTo(100,400) ;

         pDC->LineTo(676 ,400) ;

        

         pDC->MoveTo(100 ,300) ;

         pDC->LineTo(375 ,25) ;

         pDC->MoveTo(676 ,300) ;

         pDC->LineTo(406 ,25) ;

        

         pDC->MoveTo(375 ,25) ;

         pDC->LineTo(375 ,10) ;

         pDC->MoveTo(406 ,25) ;

         pDC->LineTo(406 ,10) ;

        

        

         CPen Lpen3(PS_NULL ,1 ,RGB(0,0,0)) ;//创建无边缘的画笔

         pDC->SelectObject(Lpen3) ;

         CBrush brush(RGB(0,255,0)) ;

         CBrush *OldBrush = pDC->SelectObject(&brush) ;

         for (i = 0 ;i<12;i++)

         {

                   for (int j = 1 ;j <12-i ;j++)

                   {

                            //绘制钉子

                            pDC->Ellipse(100+j*4*R-R + i*2*R ,300+R - i*2*R,100+j*4*R+R+ i*2*R ,

                                     300-R - i*2*R) ;

                            //绘制小球下落位置的提示球

                            pDC->Ellipse(85 - 4*R ,300+R - i*2*R ,85 - 2*R ,300-R - i*2*R) ;

                   }

         }

         //因为提示球的行数比钉子多三个,故还需要画3

         for (i =1 ;i<4;i++)

         {

                   CRect rectRange;

                   rectRange.left = 85 - 4*R ;

                   rectRange.top = 300 - R ;

                   rectRange.right = 85 - 2*R ;

                   rectRange.bottom = 300 + R ;

                   //绘制小球下落位置的提示球

                   pDC->Ellipse(rectRange.left ,rectRange.top + i*2*R ,

                            rectRange.right ,rectRange.bottom + i*2*R) ;

         }

         pDC->SelectObject(OldBrush) ;

         pDC->SelectObject(oldPen);//恢复原来画笔的属性

}

《3》在函数void CGEDBView::OnDraw(CDC* pDC)中添加如下代码:

void CGEDBView::OnDraw(CDC* pDC)

{

         CGEDBDoc* pDoc = GetDocument();

         ASSERT_VALID(pDoc);

         // TODO: add draw code for native data here

         pDC->SetViewportExt(800,400);//设置视口范围

         if (CGEDBView::N == CPublic::BallNumber)//实验次数大于设定的时候

         {

                   AfxMessageBox("程序结束!") ;

Return ;

         }

         DrawFrame(pDC) ;//画框架

         //总共有11块板

         const int N = 11 ;//高尔顿版板的数目

}

《4》为类CGEDBViw添加成员变量public:

         CButton m_MyButton1;

         CButton m_MyButton2;//用于创建两个按钮

CButton m_MyButton3;

添加消息响应函数OnInitialUpdate,并添加如下代码:

void CGEDBView::OnInitialUpdate()

{

         CView::OnInitialUpdate();

         //创建开始按钮

         m_MyButton1.Create("开始试验",WS_CHILD|BS_DEFPUSHBUTTON,CRect(700,0,800,50),

                   this,ID_MyButton1);

         m_MyButton1.ShowWindow(SW_SHOWNORMAL);

         //创建终止按钮

         m_MyButton2.Create("暂停试验",WS_CHILD|BS_DEFPUSHBUTTON,CRect(850,0,950,50),

                   this,ID_MyButton2);

         m_MyButton2.ShowWindow(SW_SHOWNORMAL);

         //创建退出按钮

         m_MyButton3.Create("退出",WS_CHILD|BS_DEFPUSHBUTTON,CRect(850,370,950,420),

                   this,ID_MyButton3);

         m_MyButton3.ShowWindow(SW_SHOWNORMAL);

}

《5》为类CGEDBViw添加消息响应函数OnDestroy,并添加如下代码:

void CGEDBView::OnDestroy()

{

         CView::OnDestroy();

         // TODO: Add your message handler code here

//此函数可以删除

         //KillTimer(1) ;//清除定时器

}

《6》为类CGEDBViw添加消息响应函数OnTimer,并添加如下代码:

void CGEDBView::OnTimer(UINT nIDEvent)

{

         // TODO: Add your message handler code here and/or call default

         if (CGEDBView::N == CPublic::BallNumber) //实验进行了设定的次数

         {

                   KillTimer(1) ;//销毁计时器     

                   return ;

         }

         CDC *pDC = GetDC() ;

         CGEDBView::OnDrawBall(pDC) ;//画球

         CView::OnTimer(nIDEvent);

}

《7》在CGEDBViw类中添加成员变量数组;

public:

         static int Backet[12] ;//统计各个区间的珠子的数目

static int N ;//定义实验次数

static int BallRound;

并在CGEDBViw.Cpp中初始化,在其构造函数CGEDBView::CGEDBView()前添加初始化语句,int CGEDBView::Backet[12]={0,0,0,0,0,0,0,0,0,0,0,0};

int CGEDBView::N = 0 ;//初始化计数器

int CGEDBView::BallRound = 0 ;

《8》为类CGEDBView添加成员函数OnDrawBall,并添加如下代码:

//绘制小球下落轨迹

void CGEDBView::OnDrawBall(CDC *pDC)

{

         CGEDBView::BallRound++ ;//计数器加一

         //设置每个钉子的半径为12

         int R = 12 ;

         static CRect rc(400-2*R,50,400,25) ;

        

         //先擦除上一个圆

         CPen   pen;

    pen.CreateStockObject(NULL_PEN);

    CPen   *pOld   =   pDC-> SelectObject(&pen);

    pDC->Ellipse(rc.left ,rc.top,rc.right ,rc.bottom);//把圆擦除

         pDC->SelectObject(pOld) ;

        

         //将显示小球位置提示球的颜色还原(还原为蓝色)

         CPen Lpen3(PS_NULL ,1 ,RGB(0,0,0)) ;//创建无边缘的画笔

         pDC->SelectObject(Lpen3) ;

         CBrush Lbrush(RGB(0,255,0)) ;

         pDC->SelectObject(&Lbrush) ;

         pDC->Ellipse(85 - 4*R ,rc.top,85 - 2*R ,rc.bottom) ;

 

         //重新绘制下一个圆

         //创建画笔

         CPen newpen(PS_NULL ,1 ,RGB(0,0,0)) ;

         CPen *OldPen =  pDC->SelectObject(&newpen) ;

         //创建画刷(画球)

         CBrush brush(CPublic::Ballref) ;

         CBrush *OldBrush = pDC->SelectObject(&brush) ;

        

         //前面的12步小球都可以向左或者右运动,

         //但是第131415步设置的是直接下落过程

         if (CGEDBView::BallRound == 13||CGEDBView::BallRound == 14)

         {       

                   rc.top += 2*R ;

                   rc.bottom += 2*R ;

                   pDC->Ellipse(rc.left ,rc.top,rc.right ,rc.bottom) ;

                   //将显示小球位置提示球的颜色改变

                   pDC->Ellipse(85 - 4*R ,rc.top,85 - 2*R ,rc.bottom) ;

                   pDC->SelectObject(OldBrush) ;

                   pDC->SelectObject(OldPen);//恢复原来画笔的属性

                   return ;

         }

         if (CGEDBView::BallRound == 15)

         {

                   CGEDBView::N++ ;//计数器加一

                   //更新下落的球的总数

                   CString tstr ,tstr2;

                   tstr2.Format("%-5d" ,CGEDBView::N) ;

                   tstr = "下落的小球数目" ;

                   pDC->SetBkColor(RGB(250,250,0)) ;

                   pDC->TextOut(800,100,tstr) ;

                   pDC->TextOut(820,130,tstr2) ;

                  

                   rc.top += 2*R ;

                   rc.bottom += 2*R ;

                   pDC->Ellipse(rc.left ,rc.top,rc.right ,rc.bottom) ;

                   pDC->Ellipse(85 - 4*R ,rc.top,85 - 2*R ,rc.bottom) ;

                   pDC->SelectObject(OldBrush) ;

                   pDC->SelectObject(OldPen);//恢复原来画笔的属性

                   CGEDBView::BallRound = 0 ;//计数器置零

                   return ;

         }

        

         //新一轮的开始,将小球置于高尔顿板进口处

         if (CGEDBView::BallRound == 1)

         {

                   rc.left = 400-2*R ;

                   rc.top = 50 ;

                   rc.right = 400;

                   rc.bottom = 25;

                   //画球(小球)

                   pDC->Ellipse(rc.left ,rc.top,rc.right ,rc.bottom) ;

                   pDC->Ellipse(85 - 4*R ,rc.top,85 - 2*R ,rc.bottom) ;

                   return ;

         }

        

         //参数N为该小球下落的第N(0<=N<12);参数direction为小球下落方向

         //direction=0表示向左,direction=1表示向右.

        

                   const int N = 11 ;//高尔顿版板的数目

                   //sum=0则落在最左边的区间,sum=N-1则落在最右边的区间

                   static int sum = 0 ;//小球向左一次则加零,向右一次则加一

                   int direction = 0 ;//小球在某一步的方向

                   //产生(0,1)区间上的随机数

                   float x = 0 ;

                   x = (float) (rand()%100)/100.0 ;

                   if (x >= 1-CPublic::RightChance)

                            direction = 1 ;

                   else

                            direction = 0 ;

                            sum += direction ;

                  

                   //一个小球需要15步才能落入桶中,

                   //前面的12步就可以决定落入的桶的编号

                   if (CGEDBView::BallRound  == 12)

                   {

                            CGEDBView::Backet[sum]++ ;//对应的桶的小球数目加一

CGEDBView::InitialBallAmount();//初始化公共类的BallAmount[12]数组

                            sum = 0 ;

                   }       

for (int k = 0 ;k<N+1 ;k++)

                            {

                                     CString str ;

                                     str.Format("%-4d" ,CGEDBView::Backet[k]) ;

                                     pDC->SetBkColor(RGB(250,250,0)) ;

                                     pDC->TextOut(110+k*50 ,405 ,str) ;

                            }

        

 

         if (direction == 0)//向左

         {

                   //将小球向左移动一个位置,并且向下移动一个位置

                   rc.left -= 2*R ;

                   rc.top += 2*R ;

                   rc.right -= 2*R ;

                   rc.bottom += 2*R ;

                  

         }

         else if (direction == 1)//向右

         {

                   //将小球向右移动一个位置,并且向下移动一个位置

                   rc.left += 2*R ;

                   rc.top += 2*R ;

                   rc.right += 2*R ;

                   rc.bottom += 2*R ;

 

         }

         else

         {

                   MessageBox("程序出错!") ;

                   return ;

         }

 

         pDC->Ellipse(rc.left ,rc.top,rc.right ,rc.bottom) ;

         pDC->Ellipse(85 - 4*R ,rc.top,85 - 2*R ,rc.bottom) ;

         pDC->SelectObject(OldBrush) ;

         pDC->SelectObject(OldPen);//恢复原来画笔的属性

}

《9》为类CGEDBView添加成员函数void CGEDBView::InitialBallAmount(),用于初始化公共类的数组BallAmount[12],并添加如下代码:

void CGEDBView::InitialBallAmount()

{

         const int Backet = 12 ;

         for (int i = 0 ;i<Backet ;i++)

         {

                   CPublic::BallAmount[i] = CGEDBView::Backet[i] ;

         }

}

并在GEDBView.cpp中包含头文件#include "Public.h"。

《10》打开资源中的“String Table”,在空白行上双击鼠标,这时会弹出一个ID属性对话框,在其中的ID编辑框中输入ID_MyButton1,标题:开始试验,其值为:61446再次添加,输入ID:ID_MyButton2,标题:暂停试验,其值为:61447。在编辑框中输入ID:ID_MyButton3,标题:退出,其值为:61448.

在GEDBView.h类的定义中添加如下代码:

protected:

         //{{AFX_MSG(CGEDBView)

         afx_msg void OnDestroy();

         afx_msg void OnTimer(UINT nIDEvent);

         //}}AFX_MSG

         afx_msg void OnMybut1();

         afx_msg void OnMybut2();

afx_msg void OnMybut3();

         DECLARE_MESSAGE_MAP()

在GEDBView.cpp中添加如下代码:

BEGIN_MESSAGE_MAP(CGEDBView, CView)

         //{{AFX_MSG_MAP(CGEDBView)

         ON_WM_DESTROY()

         ON_WM_TIMER()

         //}}AFX_MSG_MAP

         // Standard printing commands

         ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

         ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

         ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

         ON_BN_CLICKED(ID_MyButton1, OnMybut1)

         ON_BN_CLICKED(ID_MyButton2, OnMybut2)

ON_BN_CLICKED(ID_MyButton3, OnMybut3)

 

END_MESSAGE_MAP()

《11》在GEDBView.cpp中实现开始试验按钮与终止试验按钮的消息响应函数:

void CGEDBView::OnMybut1()

{

         AfxMessageBox("程序开始!") ;

         SetTimer(1,CPublic::Fallpace ,NULL) ;//安装定时器1

         srand((unsigned int)time(NULL));//随机数种子

}

 

void CGEDBView::OnMybut2()

{

        

         KillTimer(1) ;//清除定时器

         //每个桶中的球都置空

         for (int i = 0 ;i<12 ;i++)

         {

                   CPublic::BallAmount[i] = 0 ;

                   CGEDBView::Backet[i] = 0 ;

         }

 

         AfxMessageBox("程序暂停!") ;

}

void CGEDBView::OnMybut3()

{

         AfxMessageBox("程序结束!") ;

         ::PostMessage(AfxGetMainWnd()->GetSafeHwnd(),WM_CLOSE,0,0);

}

《10》效果如下所示:

《11》为MessageBox()消息框添加自动关闭功能

(1)在函数void CGEDBView::OnInitialUpdate() 中添加如下代码:

SetTimer(2,1000,NULL) ;//MessageBox()函数添加定时器

(2)在GEDBView.cpp中定义如下变量及常量:

#define Wait_MessageBox 2 //定义自动关闭MessageBox消息框的时间

#define Message_Title "GEDB"////消息框的标题,用于查找消息框的句柄

BOOL  fgMsgDlgShow = FALSE;       //消息框弹出标记

UINT  nMsgDlgTimer = 0;           //消息框计数器

(3)在void CGEDBView::OnTimer(UINT nIDEvent)中做如下修改

void CGEDBView::OnTimer(UINT nIDEvent)

{

         // TODO: Add your message handler code here and/or call default

         CDC *pDC = GetDC() ;

         switch(nIDEvent)

         {

         case 1:

                   if (CGEDBView::N == CPublic::BallNumber)//实验进行了设定的次数

                   {

                            KillTimer(1) ;//销毁计时器

                            return ;

                   }

                   CGEDBView::OnDrawBall(pDC) ;//画球

                   CView::OnTimer(nIDEvent);

                   break ;

         case 2:

                   if(fgMsgDlgShow)

                   {

                            nMsgDlgTimer++;

                            if(nMsgDlgTimer >= Wait_MessageBox)

                            {

                                     fgMsgDlgShow = FALSE ;//撤销标记

                                     CWnd* hWnd;

                                     hWnd = FindWindow(NULL,Message_Title); //根据标题,查找消息框句柄

                                     if(hWnd) hWnd->SendMessage(WM_CLOSE, NULL, NULL); //找到后,向其发关闭消息

                            }

                   }

        break ;

         default:

                   break ;

         }

        

}

(4)在void CGEDBView::OnMybut1()作如下修改:

void CGEDBView::OnMybut1()

{

         fgMsgDlgShow = TRUE; 

         nMsgDlgTimer = 0;

         AfxMessageBox("程序开始!") ;

CPublic::CPublic();//初始化实验设置

         SetTimer(1,CPublic::Fallpace ,NULL) ;//安装定时器1

         srand((unsigned int)time(NULL));//随机数种子

}

(5)在void CGEDBView::OnMybut2()中作如下修改:

void CGEDBView::OnMybut2()

{

        

         KillTimer(1) ;//清除定时器

         //每个桶中的球都置空

         for (int i = 0 ;i<12 ;i++)

         {

                   CPublic::BallAmount[i] = 0 ;

                   CGEDBView::Backet[i] = 0 ;

         }

         fgMsgDlgShow = TRUE; 

         nMsgDlgTimer = 0;

         AfxMessageBox("程序暂停!") ;

}

(6)在void CGEDBView::OnMybut3()中作如下修改:

void CGEDBView::OnMybut3()

{

         fgMsgDlgShow = TRUE; 

         nMsgDlgTimer = 0;

         AfxMessageBox("程序结束!") ;

         ::PostMessage(AfxGetMainWnd()->GetSafeHwnd(),WM_CLOSE,0,0);//关闭对话框

}