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步小球都可以向左或者右运动,
//但是第13、14、15步设置的是直接下落过程
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);//关闭对话框
}